openwrt_helloworld/luci-app-passwall/luasrc/passwall/util_sing-box.lua
2025-04-29 08:30:22 +08:00

1998 lines
60 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

module("luci.passwall.util_sing-box", package.seeall)
local api = require "luci.passwall.api"
local uci = api.uci
local sys = api.sys
local jsonc = api.jsonc
local appname = "passwall"
local fs = api.fs
local split = api.split
local local_version = api.get_app_version("sing-box")
local version_ge_1_11_0 = api.compare_versions(local_version:match("[^v]+"), ">=", "1.11.0")
local version_ge_1_12_0 = api.compare_versions(local_version:match("[^v]+"), ">=", "1.12.0")
local geosite_all_tag = {}
local geoip_all_tag = {}
local srss_path = "/tmp/etc/" .. appname .."_tmp/srss/"
local function convert_geofile()
if api.compare_versions(local_version:match("[^v]+"), "<", "1.8.0") then
api.log("注意Sing-Box 版本低Sing-Box 分流无法启用!请在[组件更新]中更新。")
return
end
local geo_dir = (uci:get(appname, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"):match("^(.*)/")
local geosite_path = geo_dir .. "/geosite.dat"
local geoip_path = geo_dir .. "/geoip.dat"
if not api.finded_com("geoview") then
api.log("!!!注意:缺少 Geoview 组件Sing-Box 分流无法启用!请在[组件更新]中更新。")
return
else
if api.compare_versions(api.get_app_version("geoview"), "<", "0.1.6") then
api.log("注意Geoview 组件版本低Sing-Box 分流无法启用!请在[组件更新]中更新。")
return
end
end
if not fs.access(srss_path) then
fs.mkdir(srss_path)
end
local function convert(file_path, prefix, tags)
if next(tags) and fs.access(file_path) then
local md5_file = srss_path .. prefix .. ".dat.md5"
local new_md5 = sys.exec("md5sum " .. file_path .. " 2>/dev/null | awk '{print $1}'"):gsub("\n", "")
local old_md5 = sys.exec("[ -f " .. md5_file .. " ] && head -n 1 " .. md5_file .. " | tr -d ' \t\n' || echo ''")
if new_md5 ~= "" and new_md5 ~= old_md5 then
sys.call("printf '%s' " .. new_md5 .. " > " .. md5_file)
sys.call("rm -rf " .. srss_path .. prefix .. "-*.srs" )
end
for k in pairs(tags) do
local srs_file = srss_path .. prefix .. "-" .. k .. ".srs"
if not fs.access(srs_file) then
local cmd = string.format("geoview -type %s -action convert -input '%s' -list '%s' -output '%s' -lowmem=true",
prefix, file_path, k, srs_file)
sys.exec(cmd)
--local status = fs.access(srs_file) and "成功。" or "失败!"
--api.log(string.format(" - 转换 %s:%s ... %s", prefix, k, status))
end
end
end
end
--api.log("Sing-Box 规则集转换:")
convert(geosite_path, "geosite", geosite_all_tag)
convert(geoip_path, "geoip", geoip_all_tag)
end
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
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
if proxy_table ~= nil and type(proxy_table) == "table" then
proxy_tag = proxy_table.tag or nil
end
if node.type ~= "sing-box" 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 = {
protocol = "socks",
address = "127.0.0.1",
port = new_port
}
else
if proxy_tag then
node.detour = proxy_tag
end
end
result = {
_id = node_id,
_flag = flag,
_flag_proxy_tag = proxy_tag,
tag = tag,
type = node.protocol,
server = node.address,
server_port = tonumber(node.port),
domain_strategy = node.domain_strategy,
detour = node.detour,
}
if version_ge_1_12_0 then
--https://sing-box.sagernet.org/migration/#migrate-outbound-domain-strategy-option-to-domain-resolver
result.domain_strategy = nil
result.domain_resolver = {
server = "direct",
strategy = (node.domain_strategy and node.domain_strategy ~="") and node.domain_strategy or nil
}
end
local tls = nil
if node.tls == "1" then
local alpn = nil
if node.alpn and node.alpn ~= "default" then
alpn = {}
string.gsub(node.alpn, '[^' .. "," .. ']+', function(w)
table.insert(alpn, w)
end)
end
tls = {
enabled = true,
disable_sni = false, --不要在 ClientHello 中发送服务器名称.
server_name = node.tls_serverName, --用于验证返回证书上的主机名,除非设置不安全。它还包含在 ClientHello 中以支持虚拟主机,除非它是 IP 地址。
insecure = (node.tls_allowInsecure == "1") and true or false, --接受任何服务器证书。
alpn = alpn, --支持的应用层协议协商列表,按优先顺序排列。如果两个对等点都支持 ALPN则选择的协议将是此列表中的一个如果没有相互支持的协议则连接将失败。
--min_version = "1.2",
--max_version = "1.3",
ech = {
enabled = (node.ech == "1") and true or false,
config = node.ech_config and split(node.ech_config:gsub("\\n", "\n"), "\n") or {},
pq_signature_schemes_enabled = node.pq_signature_schemes_enabled and true or false,
dynamic_record_sizing_disabled = node.dynamic_record_sizing_disabled and true or false
},
utls = {
enabled = (node.utls == "1" or node.reality == "1") and true or false,
fingerprint = node.fingerprint or "chrome"
},
reality = {
enabled = (node.reality == "1") and true or false,
public_key = node.reality_publicKey,
short_id = node.reality_shortId
}
}
end
local mux = nil
if node.mux == "1" then
mux = {
enabled = true,
protocol = node.mux_type or "h2mux",
max_connections = ( (node.tcpbrutal == "1") and 1 ) or tonumber(node.mux_concurrency) or 4,
padding = (node.mux_padding == "1") and true or false,
--min_streams = 4,
--max_streams = 0,
brutal = {
enabled = (node.tcpbrutal == "1") and true or false,
up_mbps = tonumber(node.tcpbrutal_up_mbps) or 10,
down_mbps = tonumber(node.tcpbrutal_down_mbps) or 50,
},
}
end
local v2ray_transport = nil
if node.transport == "http" then
v2ray_transport = {
type = "http",
host = { node.http_host },
path = node.http_path or "/",
idle_timeout = (node.http_h2_health_check == "1") and node.http_h2_read_idle_timeout or nil,
ping_timeout = (node.http_h2_health_check == "1") and node.http_h2_health_check_timeout or nil,
}
--不强制执行 TLS。如果未配置 TLS将使用纯 HTTP 1.1。
end
if node.transport == "ws" then
v2ray_transport = {
type = "ws",
path = node.ws_path or "/",
headers = (node.ws_host ~= nil) and { Host = node.ws_host } or nil,
max_early_data = tonumber(node.ws_maxEarlyData) or nil,
early_data_header_name = (node.ws_earlyDataHeaderName) and node.ws_earlyDataHeaderName or nil --要与 Xray-core 兼容,请将其设置为 Sec-WebSocket-Protocol。它需要与服务器保持一致。
}
end
if node.transport == "httpupgrade" then
v2ray_transport = {
type = "httpupgrade",
host = node.httpupgrade_host,
path = node.httpupgrade_path or "/",
}
end
if node.transport == "quic" then
v2ray_transport = {
type = "quic"
}
--没有额外的加密支持: 它基本上是重复加密。 并且 Xray-core 在这里与 v2ray-core 不兼容。
end
if node.transport == "grpc" then
v2ray_transport = {
type = "grpc",
service_name = node.grpc_serviceName,
idle_timeout = tonumber(node.grpc_idle_timeout) or nil,
ping_timeout = tonumber(node.grpc_health_check_timeout) or nil,
permit_without_stream = (node.grpc_permit_without_stream == "1") and true or nil,
}
end
local protocol_table = nil
if node.protocol == "socks" then
protocol_table = {
version = "5",
username = (node.username and node.password) and node.username or nil,
password = (node.username and node.password) and node.password or nil,
udp_over_tcp = node.uot == "1" and {
enabled = true,
version = 2
} or nil,
}
end
if node.protocol == "http" then
protocol_table = {
username = (node.username and node.password) and node.username or nil,
password = (node.username and node.password) and node.password or nil,
path = nil,
headers = nil,
tls = tls
}
end
if node.protocol == "shadowsocks" then
protocol_table = {
method = node.method or nil,
password = node.password or "",
plugin = (node.plugin_enabled and node.plugin) or nil,
plugin_opts = (node.plugin_enabled and node.plugin_opts) or nil,
udp_over_tcp = node.uot == "1" and {
enabled = true,
version = 2
} or nil,
multiplex = mux,
}
end
if node.protocol == "shadowsocksr" then
protocol_table = {
method = node.method or nil,
password = node.password or "",
obfs = node.ssr_obfs,
obfs_param = node.ssr_obfs_param,
protocol = node.ssr_protocol,
protocol_param = node.ssr_protocol_param,
}
end
if node.protocol == "trojan" then
protocol_table = {
password = node.password,
tls = tls,
multiplex = mux,
transport = v2ray_transport
}
end
if node.protocol == "vmess" then
protocol_table = {
uuid = node.uuid,
security = node.security,
alter_id = (node.alter_id) and tonumber(node.alter_id) or 0,
global_padding = (node.global_padding == "1") and true or false,
authenticated_length = (node.authenticated_length == "1") and true or false,
tls = tls,
packet_encoding = "", --UDP 包编码。(空):禁用 packetaddr由 v2ray 5+ 支持 xudp由 xray 支持
multiplex = mux,
transport = v2ray_transport,
}
end
if node.protocol == "vless" then
protocol_table = {
uuid = node.uuid,
flow = (node.tls == '1' and node.flow) and node.flow or nil,
tls = tls,
packet_encoding = "xudp", --UDP 包编码。(空):禁用 packetaddr由 v2ray 5+ 支持 xudp由 xray 支持
multiplex = mux,
transport = v2ray_transport,
}
end
if node.protocol == "wireguard" then
if 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
protocol_table = {
system_interface = nil,
interface_name = nil,
local_address = node.wireguard_local_address,
private_key = node.wireguard_secret_key,
peer_public_key = node.wireguard_public_key,
pre_shared_key = node.wireguard_preSharedKey,
reserved = node.wireguard_reserved,
mtu = tonumber(node.wireguard_mtu),
}
end
if node.protocol == "hysteria" then
protocol_table = {
up_mbps = tonumber(node.hysteria_up_mbps),
down_mbps = tonumber(node.hysteria_down_mbps),
obfs = node.hysteria_obfs,
auth = (node.hysteria_auth_type == "base64") and node.hysteria_auth_password or nil,
auth_str = (node.hysteria_auth_type == "string") and node.hysteria_auth_password or nil,
recv_window_conn = tonumber(node.hysteria_recv_window_conn),
recv_window = tonumber(node.hysteria_recv_window),
disable_mtu_discovery = (node.hysteria_disable_mtu_discovery == "1") and true or false,
tls = {
enabled = true,
server_name = node.tls_serverName,
insecure = (node.tls_allowInsecure == "1") and true or false,
alpn = (node.hysteria_alpn and node.hysteria_alpn ~= "") and {
node.hysteria_alpn
} or nil,
ech = {
enabled = (node.ech == "1") and true or false,
config = node.ech_config and split(node.ech_config:gsub("\\n", "\n"), "\n") or {},
pq_signature_schemes_enabled = node.pq_signature_schemes_enabled and true or false,
dynamic_record_sizing_disabled = node.dynamic_record_sizing_disabled and true or false
}
}
}
end
if node.protocol == "shadowtls" then
protocol_table = {
version = tonumber(node.shadowtls_version),
password = (node.shadowtls_version == "2" or node.shadowtls_version == "3") and node.password or nil,
tls = tls,
}
end
if node.protocol == "tuic" then
protocol_table = {
uuid = node.uuid,
password = node.password,
congestion_control = node.tuic_congestion_control or "cubic",
udp_relay_mode = node.tuic_udp_relay_mode or "native",
udp_over_stream = false,
zero_rtt_handshake = (node.tuic_zero_rtt_handshake == "1") and true or false,
heartbeat = node.tuic_heartbeat .. "s",
tls = {
enabled = true,
server_name = node.tls_serverName,
insecure = (node.tls_allowInsecure == "1") and true or false,
alpn = (node.tuic_alpn and node.tuic_alpn ~= "") and {
node.tuic_alpn
} or nil,
ech = {
enabled = (node.ech == "1") and true or false,
config = node.ech_config and split(node.ech_config:gsub("\\n", "\n"), "\n") or {},
pq_signature_schemes_enabled = node.pq_signature_schemes_enabled and true or false,
dynamic_record_sizing_disabled = node.dynamic_record_sizing_disabled and true or false
}
}
}
end
if node.protocol == "hysteria2" then
local server_ports = {}
if node.hysteria2_hop then
node.hysteria2_hop = string.gsub(node.hysteria2_hop, "-", ":")
for range in node.hysteria2_hop:gmatch("([^,]+)") do
if range:match("^%d+:%d+$") then
table.insert(server_ports, range)
end
end
end
protocol_table = {
server_ports = next(server_ports) and server_ports or nil,
hop_interval = next(server_ports) and "30s" or nil,
up_mbps = (node.hysteria2_up_mbps and tonumber(node.hysteria2_up_mbps)) and tonumber(node.hysteria2_up_mbps) or nil,
down_mbps = (node.hysteria2_down_mbps and tonumber(node.hysteria2_down_mbps)) and tonumber(node.hysteria2_down_mbps) or nil,
obfs = {
type = node.hysteria2_obfs_type,
password = node.hysteria2_obfs_password
},
password = node.hysteria2_auth_password or nil,
tls = {
enabled = true,
server_name = node.tls_serverName,
insecure = (node.tls_allowInsecure == "1") and true or false,
ech = {
enabled = (node.ech == "1") and true or false,
config = node.ech_config and split(node.ech_config:gsub("\\n", "\n"), "\n") or {},
pq_signature_schemes_enabled = node.pq_signature_schemes_enabled and true or false,
dynamic_record_sizing_disabled = node.dynamic_record_sizing_disabled and true or false
}
}
}
end
if node.protocol == "anytls" then
protocol_table = {
password = (node.password and node.password ~= "") and node.password or "",
tls = tls
}
end
if protocol_table then
for key, value in pairs(protocol_table) do
result[key] = value
end
end
end
return result
end
function gen_config_server(node)
local outbounds = {
{ type = "direct", tag = "direct" },
{ type = "block", tag = "block" }
}
local tls = {
enabled = true,
certificate_path = node.tls_certificateFile,
key_path = node.tls_keyFile,
}
if node.tls == "1" and node.reality == "1" then
tls.certificate_path = nil
tls.key_path = nil
tls.reality = {
enabled = true,
private_key = node.reality_private_key,
short_id = {
node.reality_shortId
},
handshake = {
server = node.reality_handshake_server,
server_port = tonumber(node.reality_handshake_server_port)
}
}
end
if node.tls == "1" and node.ech == "1" then
tls.ech = {
enabled = true,
key = node.ech_key and split(node.ech_key:gsub("\\n", "\n"), "\n") or {},
pq_signature_schemes_enabled = (node.pq_signature_schemes_enabled == "1") and true or false,
dynamic_record_sizing_disabled = (node.dynamic_record_sizing_disabled == "1") and true or false,
}
end
local mux = nil
if node.mux == "1" then
mux = {
enabled = true,
padding = (node.mux_padding == "1") and true or false,
brutal = {
enabled = (node.tcpbrutal == "1") and true or false,
up_mbps = tonumber(node.tcpbrutal_up_mbps) or 10,
down_mbps = tonumber(node.tcpbrutal_down_mbps) or 50,
},
}
end
local v2ray_transport = nil
if node.transport == "http" then
v2ray_transport = {
type = "http",
host = node.http_host,
path = node.http_path or "/",
}
end
if node.transport == "ws" then
v2ray_transport = {
type = "ws",
path = node.ws_path or "/",
headers = (node.ws_host ~= nil) and { Host = node.ws_host } or nil,
early_data_header_name = (node.ws_earlyDataHeaderName) and node.ws_earlyDataHeaderName or nil --要与 Xray-core 兼容,请将其设置为 Sec-WebSocket-Protocol。它需要与服务器保持一致。
}
end
if node.transport == "httpupgrade" then
v2ray_transport = {
type = "httpupgrade",
host = node.httpupgrade_host,
path = node.httpupgrade_path or "/",
}
end
if node.transport == "quic" then
v2ray_transport = {
type = "quic"
}
--没有额外的加密支持: 它基本上是重复加密。 并且 Xray-core 在这里与 v2ray-core 不兼容。
end
if node.transport == "grpc" then
v2ray_transport = {
type = "grpc",
service_name = node.grpc_serviceName,
}
end
local inbound = {
type = node.protocol,
tag = "inbound",
listen = (node.bind_local == "1") and "127.0.0.1" or "::",
listen_port = tonumber(node.port),
}
local protocol_table = nil
if node.protocol == "mixed" then
protocol_table = {
users = (node.auth == "1") and {
{
username = node.username,
password = node.password
}
} or nil,
set_system_proxy = false
}
end
if node.protocol == "socks" then
protocol_table = {
users = (node.auth == "1") and {
{
username = node.username,
password = node.password
}
} or nil
}
end
if node.protocol == "http" then
protocol_table = {
users = (node.auth == "1") and {
{
username = node.username,
password = node.password
}
} or nil,
tls = (node.tls == "1") and tls or nil,
}
end
if node.protocol == "shadowsocks" then
protocol_table = {
method = node.method,
password = node.password,
multiplex = mux,
}
end
if node.protocol == "vmess" then
if node.uuid then
local users = {}
for i = 1, #node.uuid do
users[i] = {
name = node.uuid[i],
uuid = node.uuid[i],
alterId = 0,
}
end
protocol_table = {
users = users,
tls = (node.tls == "1") and tls or nil,
multiplex = mux,
transport = v2ray_transport,
}
end
end
if node.protocol == "vless" then
if node.uuid then
local users = {}
for i = 1, #node.uuid do
users[i] = {
name = node.uuid[i],
uuid = node.uuid[i],
flow = node.flow,
}
end
protocol_table = {
users = users,
tls = (node.tls == "1") and tls or nil,
multiplex = mux,
transport = v2ray_transport,
}
end
end
if node.protocol == "trojan" then
if node.uuid then
local users = {}
for i = 1, #node.uuid do
users[i] = {
name = node.uuid[i],
password = node.uuid[i],
}
end
protocol_table = {
users = users,
tls = (node.tls == "1") and tls or nil,
fallback = nil,
fallback_for_alpn = nil,
multiplex = mux,
transport = v2ray_transport,
}
end
end
if node.protocol == "naive" then
protocol_table = {
users = {
{
username = node.username,
password = node.password
}
},
tls = tls,
}
end
if node.protocol == "hysteria" then
tls.alpn = (node.hysteria_alpn and node.hysteria_alpn ~= "") and {
node.hysteria_alpn
} or nil
protocol_table = {
up = node.hysteria_up_mbps .. " Mbps",
down = node.hysteria_down_mbps .. " Mbps",
up_mbps = tonumber(node.hysteria_up_mbps),
down_mbps = tonumber(node.hysteria_down_mbps),
obfs = node.hysteria_obfs,
users = {
{
name = "user1",
auth = (node.hysteria_auth_type == "base64") and node.hysteria_auth_password or nil,
auth_str = (node.hysteria_auth_type == "string") and node.hysteria_auth_password or nil,
}
},
recv_window_conn = node.hysteria_recv_window_conn and tonumber(node.hysteria_recv_window_conn) or nil,
recv_window_client = node.hysteria_recv_window_client and tonumber(node.hysteria_recv_window_client) or nil,
max_conn_client = node.hysteria_max_conn_client and tonumber(node.hysteria_max_conn_client) or nil,
disable_mtu_discovery = (node.hysteria_disable_mtu_discovery == "1") and true or false,
tls = tls
}
end
if node.protocol == "tuic" then
if node.uuid then
local users = {}
for i = 1, #node.uuid do
users[i] = {
name = node.uuid[i],
uuid = node.uuid[i],
password = node.password
}
end
tls.alpn = (node.tuic_alpn and node.tuic_alpn ~= "") and {
node.tuic_alpn
} or nil
protocol_table = {
users = users,
congestion_control = node.tuic_congestion_control or "cubic",
zero_rtt_handshake = (node.tuic_zero_rtt_handshake == "1") and true or false,
heartbeat = node.tuic_heartbeat .. "s",
tls = tls
}
end
end
if node.protocol == "hysteria2" then
protocol_table = {
up_mbps = (node.hysteria2_ignore_client_bandwidth ~= "1" and node.hysteria2_up_mbps and tonumber(node.hysteria2_up_mbps)) and tonumber(node.hysteria2_up_mbps) or nil,
down_mbps = (node.hysteria2_ignore_client_bandwidth ~= "1" and node.hysteria2_down_mbps and tonumber(node.hysteria2_down_mbps)) and tonumber(node.hysteria2_down_mbps) or nil,
obfs = {
type = node.hysteria2_obfs_type,
password = node.hysteria2_obfs_password
},
users = {
{
name = "user1",
password = node.hysteria2_auth_password or nil,
}
},
ignore_client_bandwidth = (node.hysteria2_ignore_client_bandwidth == "1") and true or false,
tls = tls
}
end
if node.protocol == "anytls" then
protocol_table = {
users = {
{
name = (node.username and node.username ~= "") and node.username or "sekai",
password = node.password
}
},
tls = tls,
}
end
if node.protocol == "direct" then
protocol_table = {
network = (node.d_protocol ~= "TCP,UDP") and node.d_protocol or nil,
override_address = node.d_address,
override_port = tonumber(node.d_port)
}
end
if protocol_table then
for key, value in pairs(protocol_table) do
inbound[key] = value
end
end
local route = {
rules = {
{
ip_cidr = { "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16" },
outbound = (node.accept_lan == nil or node.accept_lan == "0") and "block" or "direct"
}
}
}
if node.outbound_node then
local outbound = nil
if node.outbound_node == "_iface" and node.outbound_node_iface then
outbound = {
type = "direct",
tag = "outbound",
bind_interface = node.outbound_node_iface,
routing_mark = 255,
}
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("passwall", 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("_", ""),
address = node.outbound_node_address,
port = tonumber(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.passwall.util_sing-box").gen_outbound(nil, outbound_node_t, "outbound")
end
if outbound then
route.final = "outbound"
table.insert(outbounds, 1, outbound)
end
end
local config = {
log = {
disabled = (not node or node.log == "0") and true or false,
level = node.loglevel or "info",
timestamp = true,
--output = logfile,
},
inbounds = { inbound },
outbounds = outbounds,
route = route
}
for index, value in ipairs(config.outbounds) do
for k, v in pairs(config.outbounds[index]) do
if k:find("_") == 1 then
config.outbounds[index][k] = nil
end
end
end
if version_ge_1_11_0 then
-- Migrate logics
-- https://sing-box.sagernet.org/migration/
for i = #config.outbounds, 1, -1 do
local value = config.outbounds[i]
if value.type == "block" then
-- https://sing-box.sagernet.org/migration/#migrate-legacy-special-outbounds-to-rule-actions
table.remove(config.outbounds, i)
end
end
-- https://sing-box.sagernet.org/migration/#migrate-legacy-special-outbounds-to-rule-actions
for i = #config.route.rules, 1, -1 do
local value = config.route.rules[i]
if value.outbound == "block" then
value.action = "reject"
value.outbound = nil
end
end
end
return config
end
function gen_config(var)
local flag = var["-flag"]
local log = var["-log"] or "0"
local loglevel = var["-loglevel"] or "warn"
local logfile = var["-logfile"] or "/dev/null"
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 tcp_redir_port = var["-tcp_redir_port"]
local udp_redir_port = var["-udp_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_port = var["-direct_dns_port"]
local direct_dns_udp_server = var["-direct_dns_udp_server"]
local direct_dns_tcp_server = var["-direct_dns_tcp_server"]
local direct_dns_dot_server = var["-direct_dns_dot_server"]
local direct_dns_query_strategy = var["-direct_dns_query_strategy"]
local remote_dns_server = var["-remote_dns_server"]
local remote_dns_port = var["-remote_dns_port"]
local remote_dns_udp_server = var["-remote_dns_udp_server"]
local remote_dns_tcp_server = var["-remote_dns_tcp_server"]
local remote_dns_doh_url = var["-remote_dns_doh_url"]
local remote_dns_doh_host = var["-remote_dns_doh_host"]
local remote_dns_client_ip = var["-remote_dns_client_ip"]
local remote_dns_query_strategy = var["-remote_dns_query_strategy"]
local remote_dns_fake = var["-remote_dns_fake"]
local dns_cache = var["-dns_cache"]
local dns_socks_address = var["-dns_socks_address"]
local dns_socks_port = var["-dns_socks_port"]
local tags = var["-tags"]
local dns_domain_rules = {}
local dns = nil
local inbounds = {}
local outbounds = {}
local COMMON = {}
local singbox_settings = uci:get_all(appname, "@global_singbox[0]") or {}
local route = {
rules = {}
}
local experimental = nil
if node_id then
local node = uci:get_all(appname, node_id)
if node then
if server_host and server_port then
node.address = server_host
node.port = server_port
end
end
if local_socks_port then
local inbound = {
type = "socks",
tag = "socks-in",
listen = local_socks_address,
listen_port = tonumber(local_socks_port),
sniff = true
}
if local_socks_username and local_socks_password and local_socks_username ~= "" and local_socks_password ~= "" then
inbound.users = {
{
username = local_socks_username,
password = local_socks_password
}
}
end
table.insert(inbounds, inbound)
end
if local_http_port then
local inbound = {
type = "http",
tag = "http-in",
listen = local_http_address,
listen_port = tonumber(local_http_port)
}
if local_http_username and local_http_password and local_http_username ~= "" and local_http_password ~= "" then
inbound.users = {
{
username = local_http_username,
password = local_http_password
}
}
end
table.insert(inbounds, inbound)
end
if tcp_redir_port then
if tcp_proxy_way ~= "tproxy" then
local inbound = {
type = "redirect",
tag = "redirect_tcp",
listen = "::",
listen_port = tonumber(tcp_redir_port),
sniff = true,
sniff_override_destination = (singbox_settings.sniff_override_destination == "1") and true or false,
}
table.insert(inbounds, inbound)
else
local inbound = {
type = "tproxy",
tag = "tproxy_tcp",
network = "tcp",
listen = "::",
listen_port = tonumber(tcp_redir_port),
sniff = true,
sniff_override_destination = (singbox_settings.sniff_override_destination == "1") and true or false,
}
table.insert(inbounds, inbound)
end
end
if udp_redir_port then
local inbound = {
type = "tproxy",
tag = "tproxy_udp",
network = "udp",
listen = "::",
listen_port = tonumber(udp_redir_port),
sniff = true,
sniff_override_destination = (singbox_settings.sniff_override_destination == "1") and true or false,
}
table.insert(inbounds, inbound)
end
local function gen_urltest(_node)
local urltest_id = _node[".name"]
local urltest_tag = "urltest-" .. urltest_id
-- existing urltest
for _, v in ipairs(outbounds) do
if v.tag == urltest_tag then
return urltest_tag
end
end
-- new urltest
local ut_nodes = _node.urltest_node
local valid_nodes = {}
for i = 1, #ut_nodes do
local ut_node_id = ut_nodes[i]
local ut_node_tag = "ut-" .. ut_node_id
local is_new_ut_node = true
for _, outbound in ipairs(outbounds) do
if string.sub(outbound.tag, 1, #ut_node_tag) == ut_node_tag then
is_new_ut_node = false
valid_nodes[#valid_nodes + 1] = outbound.tag
break
end
end
if is_new_ut_node then
local ut_node = uci:get_all(appname, ut_node_id)
local outbound = gen_outbound(flag, ut_node, ut_node_tag)
if outbound then
outbound.tag = outbound.tag .. ":" .. ut_node.remarks
table.insert(outbounds, outbound)
valid_nodes[#valid_nodes + 1] = outbound.tag
end
end
end
if #valid_nodes == 0 then return nil end
local outbound = {
type = "urltest",
tag = urltest_tag,
outbounds = valid_nodes,
url = _node.urltest_url or "https://www.gstatic.com/generate_204",
interval = _node.urltest_interval and tonumber(_node.urltest_interval) and string.format("%dm", tonumber(_node.urltest_interval) / 60) or "3m",
tolerance = _node.urltest_tolerance and tonumber(_node.urltest_tolerance) and tonumber(_node.urltest_tolerance) or 50,
idle_timeout = _node.urltest_idle_timeout and tonumber(_node.urltest_idle_timeout) and string.format("%dm", tonumber(_node.urltest_idle_timeout) / 60) or "30m",
interrupt_exist_connections = (_node.urltest_interrupt_exist_connections == "true" or _node.urltest_interrupt_exist_connections == "1") and true or false
}
table.insert(outbounds, outbound)
return urltest_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.shadowtls == "1" then
local _node = {
type = "sing-box",
protocol = "shadowtls",
shadowtls_version = node.shadowtls_version,
password = (node.shadowtls_version == "2" or node.shadowtls_version == "3") and node.shadowtls_password or nil,
address = node.address,
port = node.port,
tls = "1",
tls_serverName = node.shadowtls_serverName,
utls = node.shadowtls_utls,
fingerprint = node.shadowtls_fingerprint
}
local shadowtls_outbound = gen_outbound(nil, _node, outbound.tag .. "_shadowtls")
if shadowtls_outbound then
last_insert_outbound = shadowtls_outbound
outbound.detour = outbound.tag .. "_shadowtls"
outbound.server = nil
outbound.server_port = nil
end
end
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.detour = preproxy_outbound.tag
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.detour = outbound.tag
table.insert(outbounds_table, to_outbound)
default_outTag = to_outbound.tag
end
end
end
return default_outTag, last_insert_outbound
end
if node.protocol == "_shunt" then
local rules = {}
local preproxy_rule_name = node.preproxy_enabled == "1" and "main" or nil
local preproxy_tag = preproxy_rule_name
local preproxy_node_id = preproxy_rule_name and node["main_node"] or nil
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
local rule_outboundTag
if _node_id == "_direct" then
rule_outboundTag = "direct"
elseif _node_id == "_blackhole" then
rule_outboundTag = "block"
elseif _node_id == "_default" and rule_name ~= "default" then
rule_outboundTag = "default"
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
if socks_node then
local _node = {
type = "sing-box",
protocol = "socks",
address = "127.0.0.1",
port = socks_node.port,
uot = "1",
}
local _outbound = gen_outbound(flag, _node, rule_name)
if _outbound then
table.insert(outbounds, _outbound)
rule_outboundTag = _outbound.tag
end
end
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
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)
rule_outboundTag = copied_outbound.tag
else
if use_proxy then
local pre_proxy = nil
if _node.type ~= "sing-box" then
pre_proxy = true
end
if pre_proxy then
new_port = get_new_port()
table.insert(inbounds, {
type = "direct",
tag = "proxy_" .. rule_name,
listen = "127.0.0.1",
listen_port = new_port,
override_address = _node.address,
override_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, {
inbound = {"proxy_" .. rule_name},
outbound = preproxy_tag,
})
end
end
local _outbound = gen_outbound(flag, _node, rule_name, { tag = use_proxy and preproxy_tag or nil })
if _outbound then
_outbound.tag = _outbound.tag .. ":" .. _node.remarks
rule_outboundTag, last_insert_outbound = set_outbound_detour(_node, _outbound, outbounds, rule_name)
table.insert(outbounds, _outbound)
if last_insert_outbound then
table.insert(outbounds, last_insert_outbound)
end
end
end
elseif _node.protocol == "_urltest" then
rule_outboundTag = gen_urltest(_node)
elseif _node.protocol == "_iface" then
if _node.iface then
local _outbound = {
type = "direct",
tag = rule_name .. ":" .. _node.remarks,
bind_interface = _node.iface,
routing_mark = 255,
}
table.insert(outbounds, _outbound)
rule_outboundTag = _outbound.tag
sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, _node.iface))
end
end
end
return rule_outboundTag
end
if preproxy_tag and preproxy_node_id then
local preproxy_outboundTag = gen_shunt_node(preproxy_rule_name, preproxy_node_id)
if preproxy_outboundTag then
preproxy_tag = preproxy_outboundTag
end
end
--default_node
local default_node_id = node.default_node or "_direct"
COMMON.default_outbound_tag = gen_shunt_node("default", default_node_id)
--shunt rule
uci:foreach(appname, "shunt_rules", function(e)
local outboundTag = gen_shunt_node(e[".name"])
if outboundTag and e.remarks then
if outboundTag == "default" then
outboundTag = COMMON.default_outbound_tag
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 tcp_redir_port then
if tcp_proxy_way == "tproxy" then
table.insert(inboundTag, "tproxy_tcp")
else
table.insert(inboundTag, "redirect_tcp")
end
end
if udp_redir_port then
table.insert(inboundTag, "tproxy_udp")
end
end
if e["inbound"]:find("socks") then
if local_socks_port then
table.insert(inboundTag, "socks-in")
end
end
end
local rule = {
inbound = inboundTag,
outbound = outboundTag,
protocol = protocols
}
if e.network then
local network = {}
string.gsub(e.network, '[^' .. "," .. ']+', function(w)
table.insert(network, w)
end)
rule.network = network
end
if e.source then
local source_ip_cidr = {}
local is_private = false
string.gsub(e.source, '[^' .. " " .. ']+', function(w)
if w:find("geoip") == 1 then
local _geoip = w:sub(1 + #"geoip:") --适配srs
if _geoip == "private" then
is_private = true
end
else
table.insert(source_ip_cidr, w)
end
end)
rule.source_ip_is_private = is_private and true or nil
rule.source_ip_cidr = #source_ip_cidr > 0 and source_ip_cidr or nil
if is_private or #source_ip_cidr > 0 then rule.rule_set_ip_cidr_match_source = true end
end
if e.sourcePort then
local source_port = {}
local source_port_range = {}
string.gsub(e.sourcePort, '[^' .. "," .. ']+', function(w)
if tonumber(w) and tonumber(w) >= 1 and tonumber(w) <= 65535 then
table.insert(source_port, tonumber(w))
else
table.insert(source_port_range, w)
end
end)
rule.source_port = #source_port > 0 and source_port or nil
rule.source_port_range = #source_port_range > 0 and source_port_range or nil
end
if e.port then
local port = {}
local port_range = {}
string.gsub(e.port, '[^' .. "," .. ']+', function(w)
if tonumber(w) and tonumber(w) >= 1 and tonumber(w) <= 65535 then
table.insert(port, tonumber(w))
else
table.insert(port_range, w)
end
end)
rule.port = #port > 0 and port or nil
rule.port_range = #port_range > 0 and port_range or nil
end
local rule_set_tag = {}
if e.domain_list then
local domain_table = {
outboundTag = outboundTag,
domain = {},
domain_suffix = {},
domain_keyword = {},
domain_regex = {},
rule_set = {},
}
string.gsub(e.domain_list, '[^' .. "\r\n" .. ']+', function(w)
if w:find("#") == 1 then return end
if w:find("geosite:") == 1 then
local _geosite = w:sub(1 + #"geosite:") --适配srs
geosite_all_tag[_geosite] = true
table.insert(rule_set_tag, "geosite-" .. _geosite)
table.insert(domain_table.rule_set, "geosite-" .. _geosite)
elseif w:find("regexp:") == 1 then
table.insert(domain_table.domain_regex, w:sub(1 + #"regexp:"))
elseif w:find("full:") == 1 then
table.insert(domain_table.domain, w:sub(1 + #"full:"))
elseif w:find("domain:") == 1 then
table.insert(domain_table.domain_suffix, w:sub(1 + #"domain:"))
else
table.insert(domain_table.domain_keyword, w)
end
end)
rule.domain = #domain_table.domain > 0 and domain_table.domain or nil
rule.domain_suffix = #domain_table.domain_suffix > 0 and domain_table.domain_suffix or nil
rule.domain_keyword = #domain_table.domain_keyword > 0 and domain_table.domain_keyword or nil
rule.domain_regex = #domain_table.domain_regex > 0 and domain_table.domain_regex or nil
if outboundTag then
table.insert(dns_domain_rules, api.clone(domain_table))
end
end
if e.ip_list then
local ip_cidr = {}
local is_private = false
string.gsub(e.ip_list, '[^' .. "\r\n" .. ']+', function(w)
if w:find("#") == 1 then return end
if w:find("geoip:") == 1 then
local _geoip = w:sub(1 + #"geoip:") --适配srs
if _geoip == "private" then
is_private = true
else
geoip_all_tag[_geoip] = true
table.insert(rule_set_tag, "geoip-" .. _geoip)
end
else
table.insert(ip_cidr, w)
end
end)
rule.ip_is_private = is_private and true or nil
rule.ip_cidr = #ip_cidr > 0 and ip_cidr or nil
end
rule.rule_set = #rule_set_tag > 0 and rule_set_tag or nil --适配srs
table.insert(rules, rule)
end
end)
for index, value in ipairs(rules) do
table.insert(route.rules, rules[index])
end
local rule_set = {} --适配srs
if next(geosite_all_tag) then
for k,v in pairs(geosite_all_tag) do
local srs_file = srss_path .. "geosite-" .. k ..".srs"
local _rule_set = {
tag = "geosite-" .. k,
type = "local",
format = "binary",
path = srs_file
}
table.insert(rule_set, _rule_set)
end
end
if next(geoip_all_tag) then
for k,v in pairs(geoip_all_tag) do
local srs_file = srss_path .. "geoip-" .. k ..".srs"
local _rule_set = {
tag = "geoip-" .. k,
type = "local",
format = "binary",
path = srs_file
}
table.insert(rule_set, _rule_set)
end
end
route.rule_set = #rule_set >0 and rule_set or nil
elseif node.protocol == "_urltest" then
if node.urltest_node then
COMMON.default_outbound_tag = gen_urltest(node)
end
elseif node.protocol == "_iface" then
if node.iface then
local outbound = {
type = "direct",
tag = node.remarks or node_id,
bind_interface = node.iface,
routing_mark = 255,
}
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)
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
end
end
end
if COMMON.default_outbound_tag then
route.final = COMMON.default_outbound_tag
end
if dns_listen_port then
dns = {
servers = {},
rules = {},
disable_cache = (dns_cache and dns_cache == "0") and true or false,
disable_expire = false, --禁用 DNS 缓存过期。
independent_cache = false, --使每个 DNS 服务器的缓存独立,以满足特殊目的。如果启用,将轻微降低性能。
reverse_mapping = true, --在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。
fakeip = nil,
}
if not version_ge_1_12_0 then --Migrate to new DNS server formats
table.insert(dns.servers, {
tag = "block",
address = "rcode://success",
})
end
if dns_socks_address and dns_socks_port then
default_outTag = "dns_socks_out"
table.insert(outbounds, 1, {
type = "socks",
tag = default_outTag,
server = dns_socks_address,
server_port = tonumber(dns_socks_port)
})
else
default_outTag = COMMON.default_outbound_tag
end
local remote_strategy = "prefer_ipv6"
if remote_dns_query_strategy == "UseIPv4" then
remote_strategy = "ipv4_only"
elseif remote_dns_query_strategy == "UseIPv6" then
remote_strategy = "ipv6_only"
end
local remote_server = {}
local fakedns_tag = "remote_fakeip"
if not version_ge_1_12_0 then --Migrate to new DNS server formats
remote_server = {
tag = "remote",
address_strategy = "prefer_ipv4",
strategy = remote_strategy,
address_resolver = "direct",
detour = default_outTag,
client_subnet = (remote_dns_client_ip and remote_dns_client_ip ~= "") and remote_dns_client_ip or nil,
}
if remote_dns_udp_server then
local server_port = tonumber(remote_dns_port) or 53
remote_server.address = "udp://" .. remote_dns_udp_server .. ":" .. server_port
end
if remote_dns_tcp_server then
remote_server.address = remote_dns_tcp_server
end
if remote_dns_doh_url and remote_dns_doh_host then
remote_server.address = remote_dns_doh_url
end
if remote_server.address then
table.insert(dns.servers, remote_server)
end
if remote_dns_fake then
dns.fakeip = {
enabled = true,
inet4_range = "198.18.0.0/15",
inet6_range = "fc00::/18",
}
table.insert(dns.servers, {
tag = fakedns_tag,
address = "fakeip",
strategy = remote_strategy,
})
if not experimental then
experimental = {}
end
experimental.cache_file = {
enabled = true,
store_fakeip = true,
path = api.CACHE_PATH .. "/singbox_" .. flag .. ".db"
}
end
else -- Migrate to 1.12 DNS
remote_server = {
tag = "remote",
domain_strategy = remote_strategy,
domain_resolver = "direct",
detour = default_outTag,
}
if remote_dns_udp_server then
local server_port = tonumber(remote_dns_port) or 53
remote_server.type = "udp"
remote_server.server = remote_dns_udp_server
remote_server.server_port = server_port
end
if remote_dns_tcp_server then
local server_port = tonumber(remote_dns_port) or 53
remote_server.type = "tcp"
remote_server.server = remote_dns_server
remote_server.server_port = server_port
end
if remote_dns_doh_url and remote_dns_doh_host then
local server_port = tonumber(remote_dns_port) or 443
remote_server.type = "https"
remote_server.server = remote_dns_doh_host
remote_server.server_port = server_port
end
if remote_server.server then
table.insert(dns.servers, remote_server)
end
if remote_dns_fake then
table.insert(dns.servers, {
tag = fakedns_tag,
type = "fakeip",
inet4_range = "198.18.0.0/15",
inet6_range = "fc00::/18",
})
if not experimental then
experimental = {}
end
experimental.cache_file = {
enabled = true,
store_fakeip = true,
path = api.CACHE_PATH .. "/singbox_" .. flag .. ".db"
}
end
end
local direct_strategy = "prefer_ipv6"
if direct_dns_udp_server or direct_dns_tcp_server or direct_dns_dot_server then
if direct_dns_query_strategy == "UseIPv4" then
direct_strategy = "ipv4_only"
elseif direct_dns_query_strategy == "UseIPv6" then
direct_strategy = "ipv6_only"
end
local domain = {}
local nodes_domain_text = sys.exec('uci show passwall | grep ".address=" | cut -d "\'" -f 2 | grep "[a-zA-Z]$" | sort -u')
string.gsub(nodes_domain_text, '[^' .. "\r\n" .. ']+', function(w)
table.insert(domain, w)
end)
if #domain > 0 then
table.insert(dns_domain_rules, 1, {
outboundTag = "direct",
domain = domain,
strategy = version_ge_1_12_0 and direct_strategy or nil
})
end
if not version_ge_1_12_0 then --Migrate to new DNS server formats
local direct_dns_server, port
if direct_dns_udp_server then
port = tonumber(direct_dns_port) or 53
direct_dns_server = "udp://" .. direct_dns_udp_server .. ":" .. port
elseif direct_dns_tcp_server then
port = tonumber(direct_dns_port) or 53
direct_dns_server = "tcp://" .. direct_dns_tcp_server .. ":" .. port
elseif direct_dns_dot_server then
port = tonumber(direct_dns_port) or 853
if direct_dns_dot_server:find(":") == nil then
direct_dns_server = "tls://" .. direct_dns_dot_server .. ":" .. port
else
direct_dns_server = "tls://[" .. direct_dns_dot_server .. "]:" .. port
end
end
table.insert(dns.servers, {
tag = "direct",
address = direct_dns_server,
address_strategy = "prefer_ipv6",
strategy = direct_strategy,
detour = "direct",
})
else -- Migrate to 1.12 DNS
local direct_dns_server, port, type
if direct_dns_udp_server then
port = tonumber(direct_dns_port) or 53
direct_dns_server = direct_dns_udp_server
type = "udp"
elseif direct_dns_tcp_server then
port = tonumber(direct_dns_port) or 53
direct_dns_server = direct_dns_tcp_server
type = "tcp"
elseif direct_dns_dot_server then
port = tonumber(direct_dns_port) or 853
direct_dns_server = direct_dns_dot_server
type = "tls"
end
table.insert(dns.servers, {
tag = "direct",
type = type,
server = direct_dns_server,
server_port = port,
domain_strategy = direct_strategy,
detour = "direct",
})
end
end
local default_dns_flag = "remote"
if dns_socks_address and dns_socks_port then
else
if node_id and (tcp_redir_port or udp_redir_port) then
local node = uci:get_all(appname, node_id)
if node.protocol == "_shunt" then
if node.default_node == "_direct" then
default_dns_flag = "direct"
end
end
else default_dns_flag = "direct"
end
end
if default_dns_flag == "remote" then
if remote_dns_fake then
table.insert(dns.rules, {
query_type = { "A", "AAAA" },
server = fakedns_tag
})
end
end
dns.final = default_dns_flag
if version_ge_1_12_0 then -- Migrate to 1.12 DNS
dns.strategy = (default_dns_flag == "direct") and direct_strategy or remote_strategy
end
--按分流顺序DNS
if dns_domain_rules and #dns_domain_rules > 0 then
for index, value in ipairs(dns_domain_rules) do
if value.outboundTag and (value.domain or value.domain_suffix or value.domain_keyword or value.domain_regex or value.rule_set) then
local dns_rule = {
server = value.outboundTag,
domain = (value.domain and #value.domain > 0) and value.domain or nil,
domain_suffix = (value.domain_suffix and #value.domain_suffix > 0) and value.domain_suffix or nil,
domain_keyword = (value.domain_keyword and #value.domain_keyword > 0) and value.domain_keyword or nil,
domain_regex = (value.domain_regex and #value.domain_regex > 0) and value.domain_regex or nil,
rule_set = (value.rule_set and #value.rule_set > 0) and value.rule_set or nil, --适配srs
disable_cache = false,
strategy = (version_ge_1_12_0 and value.outboundTag == "direct") and direct_strategy or nil --Migrate to 1.12 DNS
}
if version_ge_1_12_0 and value.outboundTag == "block" then --Migrate to 1.12 DNS
dns_rule.action = "predefined"
dns_rule.rcode = "NOERROR"
dns_rule.server = nil
dns_rule.disable_cache = nil
end
if value.outboundTag ~= "block" and value.outboundTag ~= "direct" then
dns_rule.server = "remote"
dns_rule.strategy = version_ge_1_12_0 and remote_strategy or nil --Migrate to 1.12 DNS
dns_rule.client_subnet = (version_ge_1_12_0 and remote_dns_client_ip and remote_dns_client_ip ~= "") and remote_dns_client_ip or nil --Migrate to 1.12 DNS
if value.outboundTag ~= COMMON.default_outbound_tag and (remote_server.address or remote_server.server) then
local remote_shunt_server = api.clone(remote_server)
remote_shunt_server.tag = value.outboundTag
remote_shunt_server.detour = value.outboundTag
table.insert(dns.servers, remote_shunt_server)
dns_rule.server = remote_shunt_server.tag
end
if remote_dns_fake then
local fakedns_dns_rule = api.clone(dns_rule)
fakedns_dns_rule.query_type = {
"A", "AAAA"
}
fakedns_dns_rule.server = fakedns_tag
fakedns_dns_rule.disable_cache = true
table.insert(dns.rules, fakedns_dns_rule)
end
end
table.insert(dns.rules, dns_rule)
end
end
end
table.insert(inbounds, {
type = "direct",
tag = "dns-in",
listen = "127.0.0.1",
listen_port = tonumber(dns_listen_port),
sniff = true,
})
table.insert(outbounds, {
type = "dns",
tag = "dns-out",
})
table.insert(route.rules, 1, {
protocol = "dns",
inbound = {
"dns-in"
},
outbound = "dns-out"
})
end
if inbounds or outbounds then
local config = {
log = {
disabled = log == "0" and true or false,
level = loglevel,
timestamp = true,
output = logfile,
},
-- DNS
dns = dns,
-- 传入连接
inbounds = inbounds,
-- 传出连接
outbounds = outbounds,
-- 路由
route = route,
--实验性
experimental = experimental,
}
local direct_outbound = {
type = "direct",
tag = "direct",
routing_mark = 255,
}
if not version_ge_1_12_0 then --Migrate to 1.12 DNS
direct_outbound.domain_strategy = "prefer_ipv6"
else
local domain_resolver = {
server = "direct",
strategy = "prefer_ipv6"
}
direct_outbound.domain_resolver = domain_resolver
-- 当没有 direct dns 服务器时添加 local
local hasDirect = false
if config.dns and config.dns.servers then
for _, server in ipairs(config.dns.servers) do
if server.tag == "direct" then
hasDirect = true
break
end
end
end
if not hasDirect then
config.dns = {
servers = {
{
type = "local",
tag = "direct"
}
},
}
end
end
table.insert(outbounds,direct_outbound)
table.insert(outbounds, {
type = "block",
tag = "block"
})
for index, value in ipairs(config.outbounds) do
if not value["_flag_proxy_tag"] and not value.detour 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
if version_ge_1_11_0 then
-- Migrate logics
-- https://sing-box.sagernet.org/migration/
local endpoints = {}
for i = #config.outbounds, 1, -1 do
local value = config.outbounds[i]
if value.type == "wireguard" then
-- https://sing-box.sagernet.org/migration/#migrate-wireguard-outbound-to-endpoint
local endpoint = {
type = "wireguard",
tag = value.tag,
system = value.system_interface,
name = value.interface_name,
mtu = value.mtu,
address = value.local_address,
private_key = value.private_key,
peers = {
{
address = value.server,
port = value.server_port,
public_key = value.peer_public_key,
pre_shared_key = value.pre_shared_key,
allowed_ips = {"0.0.0.0/0"},
reserved = value.reserved
}
},
domain_strategy = value.domain_strategy,
detour = value.detour
}
endpoints[#endpoints + 1] = endpoint
table.remove(config.outbounds, i)
end
if value.type == "block" or value.type == "dns" then
-- https://sing-box.sagernet.org/migration/#migrate-legacy-special-outbounds-to-rule-actions
table.remove(config.outbounds, i)
end
end
if #endpoints > 0 then
config.endpoints = endpoints
end
-- https://sing-box.sagernet.org/migration/#migrate-legacy-special-outbounds-to-rule-actions
for i = #config.route.rules, 1, -1 do
local value = config.route.rules[i]
if value.outbound == "block" then
value.action = "reject"
value.outbound = nil
elseif value.outbound == "dns-out" then
value.action = "hijack-dns"
value.outbound = nil
else
value.action = "route"
end
end
-- https://sing-box.sagernet.org/migration/#migrate-legacy-inbound-fields-to-rule-actions
for i = #config.inbounds, 1, -1 do
local value = config.inbounds[i]
if value.sniff == true then
table.insert(config.route.rules, 1, {
inbound = value.tag,
action = "sniff"
})
value.sniff = nil
value.sniff_override_destination = nil
end
if value.domain_strategy then
table.insert(config.route.rules, 1, {
inbound = value.tag,
action = "resolve",
strategy = value.domain_strategy,
--server = ""
})
value.domain_strategy = nil
end
end
if config.route.final == "block" then
config.route.final = nil
table.insert(config.route.rules, {
action = "reject"
})
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 = {}
if local_socks_address and local_socks_port then
local inbound = {
type = "socks",
tag = "socks-in",
listen = local_socks_address,
listen_port = tonumber(local_socks_port),
}
if local_socks_username and local_socks_password and local_socks_username ~= "" and local_socks_password ~= "" then
inbound.users = {
username = local_socks_username,
password = local_socks_password
}
end
table.insert(inbounds, inbound)
end
if local_http_address and local_http_port then
local inbound = {
type = "http",
tag = "http-in",
tls = nil,
listen = local_http_address,
listen_port = tonumber(local_http_port),
}
if local_http_username and local_http_password and local_http_username ~= "" and local_http_password ~= "" then
inbound.users = {
{
username = local_http_username,
password = local_http_password
}
}
end
table.insert(inbounds, inbound)
end
if server_proto ~= "nil" and server_address ~= "nil" and server_port ~= "nil" then
local outbound = {
type = server_proto,
tag = "out",
server = server_address,
server_port = tonumber(server_port),
username = (server_username and server_password) and server_username or nil,
password = (server_username and server_password) and server_password or nil,
}
if outbound then table.insert(outbounds, outbound) end
end
local config = {
log = {
disabled = true,
level = "warn",
timestamp = true,
},
-- 传入连接
inbounds = inbounds,
-- 传出连接
outbounds = outbounds,
}
return jsonc.stringify(config, 1)
end
_G.gen_config = gen_config
_G.gen_proto_config = gen_proto_config
if arg[1] then
local func =_G[arg[1]]
if func then
print(func(api.get_function_args(arg)))
if next(geosite_all_tag) or next(geoip_all_tag) then
convert_geofile()
end
end
end