mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-07-28 03:01:54 +08:00
973 lines
32 KiB
Lua
973 lines
32 KiB
Lua
#!/usr/bin/lua
|
|
local ucursor = require "luci.model.uci"
|
|
local json = require "luci.jsonc"
|
|
local nixiofs = require "nixio.fs"
|
|
|
|
local general_section = ucursor:get_first("xjay", "general")
|
|
local general = ucursor:get_all("xjay", general_section)
|
|
|
|
local inbound_section = ucursor:get_first("xjay", "inbound")
|
|
local inbound = ucursor:get_all("xjay", inbound_section)
|
|
|
|
local outbound_section = ucursor:get_first("xjay", "outbound")
|
|
local outbound = ucursor:get_all("xjay", outbound_section)
|
|
|
|
local dns_section = ucursor:get_first("xjay", "dns")
|
|
local dns = ucursor:get_all("xjay", dns_section)
|
|
|
|
local routing_section = ucursor:get_first("xjay", "routing")
|
|
local routing = ucursor:get_all("xjay", routing_section)
|
|
|
|
local misc_section = ucursor:get_first("xjay", "misc")
|
|
local misc = ucursor:get_all("xjay", misc_section)
|
|
|
|
local function tcp_settings(data, direction)
|
|
if data.stream_network == "tcp" then
|
|
local acceptproxyprotocal = data.tcp_acceptproxyprotocol == "true" and true or false
|
|
|
|
return {
|
|
acceptProxyProtocol = direction == "inbound" and acceptproxyprotocal or nil,
|
|
header = {
|
|
type = data.tcp_type,
|
|
request = data.tcp_type == "http" and {
|
|
version = "1.1",
|
|
method = "GET",
|
|
path = data.http_path,
|
|
headers = {
|
|
Host = data.http_host,
|
|
User_Agent = {
|
|
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36",
|
|
"Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46"
|
|
},
|
|
Accept_Encoding = {"gzip, deflate"},
|
|
Connection = {"keep-alive"},
|
|
Pragma = "no-cache"
|
|
}
|
|
} or nil,
|
|
response = data.tcp_type == "http" and {
|
|
version = "1.1",
|
|
status = "200",
|
|
reason = "OK",
|
|
headers = {
|
|
Content_Type = {"application/octet-stream", "video/mpeg"},
|
|
Transfer_Encoding = {"chunked"},
|
|
Connection = {"keep-alive"},
|
|
Pragma = "no-cache"
|
|
}
|
|
} or nil
|
|
} or nil
|
|
}
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
local function kcp_settings(data, direction)
|
|
if data.stream_network == "kcp" then
|
|
return {
|
|
mtu = data.kcp_mtu ~= nil and tonumber(data.kcp_mtu) or nil,
|
|
tti = data.kcp_tti ~= nil and tonumber(data.kcp_tti) or nil,
|
|
uplinkCapacity = data.kcp_uplinkcapacity ~= nil and tonumber(data.kcp_uplinkcapacity) or nil,
|
|
downlinkCapacity = data.kcp_downlinkcapacity ~= nil and tonumber(data.kcp_downlinkcapacity) or nil,
|
|
congestion = data.kcp_congestion == "true" and true or false,
|
|
readBufferSize = data.kcp_readbuffersize ~= nil and tonumber(data.kcp_readbuffersize) or nil,
|
|
writeBufferSize = data.kcp_writebuffersize ~= nil and tonumber(data.kcp_writebuffersize) or nil,
|
|
seed = data.kcp_seed,
|
|
header = {
|
|
type = data.kcp_type
|
|
}
|
|
}
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
local function ws_settings(data, direction)
|
|
if data.stream_network == "ws" then
|
|
local acceptproxyprotocal = nil
|
|
|
|
if direction == "inbound" then
|
|
acceptproxyprotocal = data.ws_acceptproxyprotocol == "true" and true or false
|
|
end
|
|
|
|
return {
|
|
acceptProxyProtocol = acceptproxyprotocal,
|
|
path = data.ws_path,
|
|
headers = data.ws_host ~= nil and {
|
|
Host = data.ws_host
|
|
} or nil
|
|
}
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
local function http_settings(data, direction)
|
|
if data.stream_network == "h2" then
|
|
local readidletimeout = nil
|
|
local healthchecktimeout = nil
|
|
local http_settings = {}
|
|
|
|
http_settings.host = data.http_host
|
|
http_settings.path = data.http_path
|
|
if direction == "outbound" then
|
|
http_settings.read_idle_timeout = data.http_readidletimeout ~= nil and tonumber(data.http_readidletimeout) or nil
|
|
http_settings.health_check_timeout = data.http_healthchecktimeout ~= nil and tonumber(data.http_healthchecktimeout) or nil
|
|
end
|
|
http_settings.method = data.http_method
|
|
-- headers not implemented yet
|
|
|
|
return next(http_settings) ~= nil and http_settings or nil
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
local function quic_settings(data, direction)
|
|
if data.stream_network == "quic" then
|
|
return {
|
|
security = data.quic_security,
|
|
key = data.quic_key,
|
|
header = {
|
|
type = data.quic_type
|
|
}
|
|
}
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
local function ds_settings(data, direction)
|
|
if data.stream_network == "ds" and direction == "inbound" then
|
|
return {
|
|
path = data.ds_path,
|
|
abstract = data.ds_abstract == "true" and true or false,
|
|
padding = data.ds_padding == "true" and true or false
|
|
}
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
local function grpc_settings(data, direction)
|
|
if (data.stream_network == "grpc") then
|
|
local multimode = nil
|
|
local idletimeout = nil
|
|
local healthchecktimeout = nil
|
|
local initialwindowssize = nil
|
|
local useragent = nil
|
|
|
|
if direction == "outbound" then
|
|
multimode = data.grpc_multimode == "true" and true or false
|
|
idletimeout = data.grpc_idletimeout ~= nil and tonumber(data.grpc_idletimeout) or nil
|
|
healthchecktimeout = data.grpc_healthchecktimeout ~= nil and tonumber(data.grpc_healthchecktimeout) or nil
|
|
permitwithoutstream = data.grpc_permitwithoutstream == "true" and true or false
|
|
initialwindowssize = data.grpc_initialwindownsize ~= nil and tonumber(data.grpc_initialwindownsize) or nil
|
|
useragent = data.grpc_useragent
|
|
end
|
|
|
|
return {
|
|
serviceName = data.grpc_servicename,
|
|
multiMode = multimode,
|
|
idle_timeout = idletimeout,
|
|
health_check_timeout = healthchecktimeout,
|
|
permit_without_stream = permitwithoutstream,
|
|
initial_windows_size = initialwindowssize,
|
|
user_agent = useragent
|
|
}
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
local function tls_settings(data, direction)
|
|
local result = {}
|
|
|
|
if direction == "outbound" then
|
|
local pinnedpeercertificatechainsha256 = {}
|
|
|
|
for _, x in ipairs(data.pinnedpeercertificatechainsha256 ~= nil and data.pinnedpeercertificatechainsha256 or {}) do
|
|
table.insert(pinnedpeercertificatechainsha256, x)
|
|
end
|
|
|
|
result.serverName = data.tls_servername
|
|
result.allowInsecure = data.tls_allowinsecure == "true" and true or false
|
|
result.disableSystemRoot = data.tls_disablesystemroot == "true" and true or false
|
|
result.enableSessionResumption = data.tls_enablesessionresumption == "true" and true or false
|
|
result.fingerprint = data.tls_fingerprint or ""
|
|
result.pinnedPeerCertificateChainSha256 = next(pinnedpeercertificatechainsha256) ~= nil and pinnedpeercertificatechainsha256 or nil
|
|
end
|
|
|
|
if direction == "inbound" then
|
|
local ciphersuites = {}
|
|
|
|
for i, v in ipairs(data.tls_ciphersuites ~= nil and data.tls_ciphersuites or {}) do
|
|
if i > 1 then v = ":" .. v end
|
|
ciphersuites[#ciphersuites+1] = tostring(v)
|
|
end
|
|
|
|
result.rejectUnknownSni = data.tls_rejectnuknownsni == "true" and true or false
|
|
result.minVersion = data.tls_minversion
|
|
result.maxVersion = data.tls_maxversion
|
|
result.cipherSuites = next(ciphersuites) and table.concat(ciphersuites) or nil
|
|
result.certificates = {
|
|
{
|
|
ocspStapling = data.cert_ocspStapling ~= nil and tonumber(data.cert_ocspStapling) or nil,
|
|
oneTimeLoading = data.cert_onetimeloading == "true" and true or false,
|
|
usage = data.cert_usage,
|
|
certificateFile = data.cert_certificatefile,
|
|
keyFile = data.cert_certificatekeyfile
|
|
}
|
|
}
|
|
end
|
|
|
|
if data.tls_alpn ~= nil then
|
|
local alpn = {}
|
|
for _, x in ipairs(data.tls_alpn) do
|
|
table.insert(alpn, x)
|
|
end
|
|
result.alpn = alpn
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
local function reality_settings(data, direction)
|
|
local result = {}
|
|
|
|
result.show = data.reality_show == "true" and true or false
|
|
|
|
if direction == "outbound" then
|
|
result.serverName = data.reality_servername
|
|
result.fingerprint = data.reality_fingerprint
|
|
result.shortId = data.reality_shortid
|
|
result.publicKey = data.reality_publickey
|
|
result.spiderX = data.reality_spiderx or ""
|
|
end
|
|
|
|
if direction == "inbound" then
|
|
local servernames = {}
|
|
local shortids = {}
|
|
|
|
for _, x in ipairs(data.reality_servername ~= nil and data.reality_servername or {}) do
|
|
table.insert(servernames, x)
|
|
end
|
|
for _, x in ipairs(data.reality_shortid ~= nil and data.reality_shortid or {}) do
|
|
table.insert(shortids, x)
|
|
end
|
|
|
|
result.dest = data.reality_dest
|
|
result.xver = data.reality_xver ~= nil and tonumber(data.reality_xver) or nil
|
|
result.serverNames = next(servernames) ~= nil and servernames or nil
|
|
result.privateKey = data.reality_privatekey
|
|
result.minClientVer = data.reality_minclientver
|
|
result.maxClientVer = data.reality_maxclientver
|
|
result.maxTimeDiff = data.reality_maxtimediff ~= nil and tonumber(data.reality_maxtimediff) or nil
|
|
result.shortIds = next(shortids) ~= nil and shortids or nil
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
local function sockopt_settings(data, direction)
|
|
local tproxy = nil
|
|
local acceptproxyprotocal = nil
|
|
local tcpcongestion = nil
|
|
local dialerproxy = nil
|
|
local mark = nil
|
|
local tcpkeepaliveinterval = nil
|
|
local interface = nil
|
|
|
|
if direction == "inbound" then
|
|
tproxy = data.sockopt_tproxy
|
|
acceptproxyprotocal = data.sockopt_acceptproxyprotocol == "true" and true or false
|
|
tcpcongestion = data.sockopt_tcpcongestion
|
|
elseif direction == "outbound" then
|
|
dialerproxy = data.sockopt_dialerproxy
|
|
mark = outbound.sockopt_mark ~= nil and tonumber(outbound.sockopt_mark) or nil
|
|
tcpkeepaliveinterval = data.sockopt_tcpkeepaliveinterval ~= nil and tonumber(data.sockopt_tcpkeepaliveinterval) or nil
|
|
interface = data.sockopt_interface
|
|
end
|
|
|
|
return {
|
|
mark = mark,
|
|
tcpFastOpen = data.sockopt_tcpfastopen == "true" and true or false,
|
|
tproxy = tproxy,
|
|
domainStrategy = data.sockopt_domainstrategy,
|
|
dialerProxy = dialerproxy,
|
|
acceptProxyProtocol = acceptproxyprotocal,
|
|
tcpKeepAliveInterval =tcpkeepaliveinterval,
|
|
tcpcongestion = tcpcongestion,
|
|
interface = interface
|
|
}
|
|
end
|
|
|
|
local function mux_settings(data, direction)
|
|
if data.mux_enabled == "true" and direction == 'outbound' then
|
|
return {
|
|
enabled = data.mux_enabled == "true" and true or false,
|
|
concurrency = data.mux_concurrency ~= nil and tonumber(data.mux_concurrency) or nil,
|
|
xudpConcurrency = data.mux_xudpconcurrency ~= nil and tonumber(data.mux_xudpconcurrency) or nil,
|
|
xudpProxyUDP443 = data.mux_xudpproxyudp443
|
|
}
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
local function stream_settings(data, direction)
|
|
return {
|
|
network = data.stream_network,
|
|
security = data.stream_security,
|
|
tlsSettings = data.stream_security == "tls" and tls_settings(data, direction) or nil,
|
|
xtlsSettings = data.stream_security == "xtls" and tls_settings(data, direction) or nil,
|
|
realitySettings = data.stream_security == "reality" and reality_settings(data, direction) or nil,
|
|
tcpSettings = tcp_settings(data, direction),
|
|
kcpSettings = kcp_settings(data, direction),
|
|
wsSettings = ws_settings(data, direction),
|
|
httpSettings = http_settings(data, direction),
|
|
quicSettings = quic_settings(data, direction),
|
|
dsSettings = ds_settings(data, direction),
|
|
grpcSettings = grpc_settings(data, direction),
|
|
sockopt = sockopt_settings(data, direction)
|
|
}
|
|
end
|
|
|
|
local function sniffing(data)
|
|
local destoverride = {}
|
|
local domainsexcluded = {}
|
|
|
|
for i, v in ipairs(data.sniffing_destoverride ~= nil and data.sniffing_destoverride or {}) do
|
|
table.insert(destoverride, v)
|
|
end
|
|
|
|
for i, v in ipairs(data.sniffing_domainsexcluded ~= nil and data.sniffing_domainsexcluded or {}) do
|
|
table.insert(domainsexcluded, v)
|
|
end
|
|
|
|
return data.sniffing_enabled == "true" and {
|
|
enabled = true,
|
|
destOverride = next(destoverride) and destoverride or nil,
|
|
metadataOnly = data.sniffing_metadataonly == "true" and true or false,
|
|
domainsExcluded = next(domainsexcluded) and domainsexcluded or nil,
|
|
routeOnly = data.sniffing_routeonly == "true" and true or false,
|
|
} or nil
|
|
end
|
|
|
|
local function fallback(data)
|
|
local fallbacks = {}
|
|
|
|
for i, v in ipairs(data.fallback ~= nil and data.fallback or {}) do
|
|
ucursor:foreach("xjay", "fallback", function(fb)
|
|
if fb.tag == v and fb.dest ~= nil then
|
|
local fallback = {
|
|
name = fb.name,
|
|
alpn = fb.alpn,
|
|
path = fb.path,
|
|
xver = fb.xver ~= nil and tonumber(fb.xver) or nil
|
|
}
|
|
|
|
-- if the dest is only a port number, then convert it to number
|
|
if string.match(fb.dest, "%d+") == fb.dest then
|
|
fallback.dest = tonumber(fb.dest)
|
|
else
|
|
fallback.dest = fb.dest
|
|
end
|
|
|
|
table.insert(fallbacks, fallback)
|
|
end
|
|
end)
|
|
end
|
|
|
|
return next(fallbacks) ~= nil and fallbacks or nil
|
|
end
|
|
|
|
local function dokodemo_inbound(data)
|
|
local network = {}
|
|
|
|
for i, v in ipairs(data.dokodemo_network ~= nil and data.dokodemo_network or {}) do
|
|
if i > 1 then v = "," .. v end
|
|
network[#network+1] = tostring(v)
|
|
end
|
|
|
|
return {
|
|
address = data.dokodemo_address or nil,
|
|
port = data.dokodemo_port ~= nil and tonumber(data.dokodemo_port) or nil,
|
|
network = next(network) and table.concat(network) or nil,
|
|
timeout = data.dokodemo_timeout ~= nil and tonumber(v.dokodemo_timeout) or nil,
|
|
followRedirect = data.dokodemo_followredirect == "true" and true or false,
|
|
userLevel = data.dokodemo_level ~= nil and tonumber(data.dokodemo_level) or nil,
|
|
fallbacks = fallback(data)
|
|
}
|
|
end
|
|
|
|
local function http_inbound(data)
|
|
local accounts = {}
|
|
|
|
for i, v in ipairs(data.http_user ~= nil and data.http_user or {}) do
|
|
ucursor:foreach("xjay", "user", function(u)
|
|
if u.name == v then
|
|
local user = {
|
|
user = u.name,
|
|
pass = u.password
|
|
}
|
|
table.insert(accounts, user)
|
|
end
|
|
end)
|
|
end
|
|
|
|
return {
|
|
timeout = data.http_timeout ~= nil and tonumber(data.http_timeout) or nil,
|
|
allowTransparent = data.http_allocatransparent == "true" and true or false,
|
|
accounts = data.http_auth == 'password' and accounts or nil,
|
|
userLevel = data.http_level ~= nil and tonumber(data.http_level) or nil,
|
|
fallbacks = fallback(data)
|
|
}
|
|
end
|
|
|
|
local function shadowsocks_inbound(data)
|
|
local network = {}
|
|
local clients = {}
|
|
|
|
for i, v in ipairs(data.ss_user ~= nil and data.ss_user or {}) do
|
|
ucursor:foreach("xjay", "user", function(u)
|
|
if u.name == v then
|
|
local user = {
|
|
password = u.password,
|
|
method = data.ss_method,
|
|
email = u.email,
|
|
level = u.level ~= nil and tonumber(u.level) or nil
|
|
}
|
|
table.insert(clients, user)
|
|
end
|
|
end)
|
|
end
|
|
|
|
for i, v in ipairs(data.ss_network == nil and {} or data.ss_network) do
|
|
if i > 1 then v = "," .. v end
|
|
network[#network+1] = tostring(v)
|
|
end
|
|
|
|
return {
|
|
clients = next(clients) and clients or nil,
|
|
network = next(network) and table.concat(network) or nil,
|
|
fallbacks = fallback(data)
|
|
}
|
|
end
|
|
|
|
local function socks_inbound(data)
|
|
local accounts = {}
|
|
|
|
for i, v in ipairs(data.socks_user ~= nil and data.socks_user or {}) do
|
|
ucursor:foreach("xjay", "user", function(u)
|
|
if u.name == v then
|
|
local user = {
|
|
user = u.name,
|
|
pass = u.password
|
|
}
|
|
table.insert(accounts, user)
|
|
end
|
|
end)
|
|
end
|
|
|
|
return {
|
|
auth = data.socks_auth,
|
|
accounts = data.socks_auth == 'password' and accounts or nil,
|
|
udp = data.socks_udp == "true" and true or false,
|
|
ip = data.socks_ip,
|
|
userLevel = data.socks_level ~= nil and tonumber(data.socks_level) or nil,
|
|
fallbacks = fallback(data)
|
|
}
|
|
end
|
|
|
|
local function trojan_inbound(data)
|
|
local clients = {}
|
|
|
|
for i, v in ipairs(data.trojan_user ~= nil and data.trojan_user or {}) do
|
|
ucursor:foreach("xjay", "user", function(u)
|
|
if u.name == v then
|
|
local user = {
|
|
password = u.password,
|
|
email = u.email,
|
|
level = u.level ~= nil and tonumber(u.level) or nil
|
|
}
|
|
table.insert(clients, user)
|
|
end
|
|
end)
|
|
end
|
|
|
|
return {
|
|
clients = next(clients) and clients or nil,
|
|
fallbacks = fallback(data)
|
|
}
|
|
end
|
|
|
|
local function vless_inbound(data)
|
|
local clients = {}
|
|
|
|
for i, v in ipairs(data.vless_user ~= nil and data.vless_user or {}) do
|
|
ucursor:foreach("xjay", "user", function(u)
|
|
if u.name == v then
|
|
local user = {
|
|
id = u.password,
|
|
flow = data.vless_flow ~= "none" and data.vless_flow or "",
|
|
email = u.email,
|
|
level = u.level ~= nil and tonumber(u.level) or nil
|
|
}
|
|
table.insert(clients, user)
|
|
end
|
|
end)
|
|
end
|
|
|
|
return {
|
|
clients = next(clients) and clients or nil,
|
|
decryption = "none",
|
|
fallbacks = fallback(data)
|
|
}
|
|
end
|
|
|
|
local function vmess_inbound(data)
|
|
local clients = {}
|
|
|
|
for i, v in ipairs(data.vmess_user ~= nil and data.vmess_user or {}) do
|
|
ucursor:foreach("xjay", "user", function(u)
|
|
if u.name == v then
|
|
local user = {
|
|
id = u.password,
|
|
alterId = data.vmess_alterid ~= nil and tonumber(data.vmess_alterid) or nil,
|
|
email = u.email,
|
|
level = u.level ~= nil and tonumber(u.level) or nil
|
|
}
|
|
table.insert(clients, user)
|
|
end
|
|
end)
|
|
end
|
|
|
|
return {
|
|
clients = next(clients) and clients or nil,
|
|
default = {
|
|
alterId = 0,
|
|
level = 0
|
|
},
|
|
detour = data.vmess_detour ~= nil and {
|
|
to = data.vmess_detour
|
|
} or nil,
|
|
fallbacks = fallback(data)
|
|
}
|
|
end
|
|
|
|
local function inbound_service(data)
|
|
local inbound_settings = {}
|
|
if data.protocol == "dokodemo-door" then
|
|
inbound_settings = dokodemo_inbound(data)
|
|
elseif data.protocol == "http" then
|
|
inbound_settings = http_inbound(data)
|
|
elseif data.protocol == "shadowsocks" then
|
|
inbound_settings = shadowsocks_inbound(data)
|
|
elseif data.protocol == "socks" then
|
|
inbound_settings = socks_inbound(data)
|
|
elseif data.protocol == "trojan" then
|
|
inbound_settings = trojan_inbound(data)
|
|
elseif data.protocol == "vless" then
|
|
inbound_settings = vless_inbound(data)
|
|
elseif data.protocol == "vmess" then
|
|
inbound_settings = vmess_inbound(data)
|
|
end
|
|
return {
|
|
tag = data.tag,
|
|
listen = data.listen,
|
|
port = tonumber(data.port),
|
|
protocol = data.protocol,
|
|
settings = inbound_settings,
|
|
streamSettings = stream_settings(data, "inbound"),
|
|
sniffing = sniffing(data)
|
|
}
|
|
end
|
|
|
|
local function blackhole_outbound()
|
|
return {
|
|
tag = "blackhole",
|
|
protocol = "blackhole"
|
|
}
|
|
end
|
|
|
|
local function dns_outbound()
|
|
return {
|
|
protocol = "dns",
|
|
tag = "dns",
|
|
streamSettings = {
|
|
sockopt = {
|
|
mark = outbound.sockopt_mark ~= nil and tonumber(outbound.sockopt_mark) or nil
|
|
}
|
|
}
|
|
}
|
|
end
|
|
|
|
local function direct_outbound()
|
|
return {
|
|
protocol = "freedom",
|
|
tag = "direct",
|
|
streamSettings = {
|
|
sockopt = {
|
|
mark = outbound.sockopt_mark ~= nil and tonumber(outbound.sockopt_mark) or nil
|
|
}
|
|
}
|
|
}
|
|
end
|
|
|
|
local function http_outbound(data)
|
|
return {
|
|
servers = {
|
|
{
|
|
address = data.address,
|
|
port = tonumber(data.port),
|
|
users = {
|
|
{
|
|
user = data.http_user,
|
|
pass = data.http_pass
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
end
|
|
|
|
local function shadowsocks_outbound(data)
|
|
return {
|
|
servers = {
|
|
{
|
|
address = data.address,
|
|
port = tonumber(data.port),
|
|
method = data.ss_method, -- encryption algorithim
|
|
password = data.ss_password,
|
|
uot = data.ss_uot == "true" and true or false, -- udp over tcp
|
|
email = data.ss_email or nil, -- optional, for identifying a user
|
|
level = data.ss_level ~= nil and tonumber(data.ss_level) or nil
|
|
}
|
|
}
|
|
}
|
|
end
|
|
|
|
local function socks_outbound(data)
|
|
return {
|
|
servers = {
|
|
{
|
|
address = data.address,
|
|
port = tonumber(data.port),
|
|
users = {
|
|
{
|
|
user = data.socks_user,
|
|
pass = data.socks_pass,
|
|
level = data.socks_level ~= nil and tonumber(data.socks_level) or nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
end
|
|
|
|
local function trojan_outbound(data)
|
|
return {
|
|
servers = {
|
|
{
|
|
address = data.address,
|
|
port = tonumber(data.port),
|
|
password = data.trojan_password,
|
|
email = data.trojan_email or nil, -- optional, for identifying a user
|
|
level = data.trojan_level ~= nil and tonumber(data.trojan_level) or nil
|
|
}
|
|
}
|
|
}
|
|
end
|
|
|
|
local function vless_outbound(data)
|
|
return {
|
|
vnext = {
|
|
{
|
|
address = data.address,
|
|
port = tonumber(data.port),
|
|
users = {
|
|
{
|
|
id = data.vless_id,
|
|
flow = data.vless_flow ~= "none" and data.vless_flow or "",
|
|
encryption = data.vless_encryption, -- encryption algorithim, for vless, currently only none
|
|
level = data.vless_level ~= nil and tonumber(data.vless_level) or nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
end
|
|
|
|
local function vmess_outbound(data)
|
|
return {
|
|
vnext = {
|
|
{
|
|
address = data.address,
|
|
port = tonumber(data.port),
|
|
users = {
|
|
{
|
|
id = data.vmess_id,
|
|
alterId = data.vmess_alterid ~= nil and tonumber(data.vmess_alterid) or nil,
|
|
security = data.vmess_security, -- encryption algorithim
|
|
level = data.vmess_level ~= nil and tonumber(data.vmess_level) or nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
end
|
|
|
|
local function outbound_server(data)
|
|
local outbound_settings = {}
|
|
|
|
if data.protocol == "http" then
|
|
outbound_settings = http_outbound(data)
|
|
elseif data.protocol == "shadowsocks" then
|
|
outbound_settings = shadowsocks_outbound(data)
|
|
elseif data.protocol == "socks" then
|
|
outbound_settings = socks_outbound(data)
|
|
elseif data.protocol == "trojan" then
|
|
outbound_settings = trojan_outbound(data)
|
|
elseif data.protocol == "vless" then
|
|
outbound_settings = vless_outbound(data)
|
|
elseif data.protocol == "vmess" then
|
|
outbound_settings = vmess_outbound(data)
|
|
end
|
|
|
|
return {
|
|
tag = data.tag,
|
|
sendThrough = data.sendthrough,
|
|
protocol = data.protocol,
|
|
settings = outbound_settings,
|
|
streamSettings = stream_settings(data, "outbound"),
|
|
mux = mux_settings(data, 'outbound')
|
|
}
|
|
end
|
|
|
|
local function logging()
|
|
return {
|
|
access = misc.log_access == "true" and "" or "none",
|
|
error = misc.log_error == "true" and "" or "none",
|
|
loglevel = misc.log_level or "warning",
|
|
dnsLog = misc.log_dnslog == "true" and true or false
|
|
}
|
|
end
|
|
|
|
local function inbounds()
|
|
local inbounds = {}
|
|
ucursor:foreach("xjay", "inbound_service", function(data)
|
|
table.insert(inbounds, inbound_service(data))
|
|
end)
|
|
return inbounds
|
|
end
|
|
|
|
local function outbounds()
|
|
local outbounds = {}
|
|
local function is_outbound_created(tag)
|
|
for i, v in ipairs(outbounds) do
|
|
if tag == v.tag then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- generating outbounds based on ruouting outbound tags
|
|
ucursor:foreach("xjay", "outbound_server", function(data)
|
|
-- insert default oubound in the first
|
|
if data.tag == outbound.default_outbound then table.insert(outbounds, 1, outbound_server(data)) end
|
|
|
|
-- append other outbounds from routing rules in the last
|
|
ucursor:foreach("xjay", "routing_rule", function(dd)
|
|
if data.tag == dd.rule_outboundtag and not is_outbound_created(dd.rule_outboundtag) then
|
|
table.insert(outbounds, outbound_server(data))
|
|
end
|
|
end)
|
|
end)
|
|
|
|
-- generating some pre-defined outbounds
|
|
if outbound.default_outbound == "direct" then
|
|
table.insert(outbounds, 1, direct_outbound())
|
|
else
|
|
table.insert(outbounds, direct_outbound())
|
|
end
|
|
|
|
if outbound.default_outbound == "blackhole" then
|
|
table.insert(outbounds, 1, blackhole_outbound())
|
|
else
|
|
table.insert(outbounds, blackhole_outbound())
|
|
end
|
|
|
|
table.insert(outbounds, dns_outbound())
|
|
|
|
return outbounds
|
|
end
|
|
|
|
local function dnss()
|
|
local hosts = {}
|
|
local servers = {}
|
|
|
|
for i, v in ipairs(dns.host == nil and {} or dns.host) do
|
|
-- break string "/dns.google/8.8.8.8" into table tab = { "dns.google", "8.8.8.8" }
|
|
local tab = {}
|
|
for w in v:gmatch("([^/]+)") do table.insert(tab, w) end
|
|
-- then add them as key value pair into hosts table
|
|
-- will be hosts = { dns.google = "8.8.8.8" }
|
|
-- not elegant but working
|
|
hosts[ tab[1] ] = tab[2]
|
|
end
|
|
|
|
ucursor:foreach("xjay", "dns_server", function(data)
|
|
local domains = {}
|
|
local expectedips = {}
|
|
local function is_domain_inserted(domain)
|
|
for i, v in ipairs(domains) do
|
|
if domain == v then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
for i, v in ipairs(data.server_domain == nil and {} or data.server_domain) do
|
|
table.insert(domains, v)
|
|
end
|
|
|
|
-- get domains from routing rules domain list
|
|
ucursor:foreach("xjay", "routings", function(dd)
|
|
if data.alias == dd.dns_server then
|
|
for i, v in ipairs(dd.rule_domain == nil and {} or dd.rule_domain) do
|
|
if not is_domain_inserted(v) then table.insert(domains, v) end
|
|
end
|
|
end
|
|
end)
|
|
|
|
for i, v in ipairs(data.server_expectedip == nil and {} or data.server_expectedip) do
|
|
table.insert(expectedips, v)
|
|
end
|
|
|
|
local server = {
|
|
queryStrategy = data.querystrategy,
|
|
address = data.server_address ~= nil and data.server_address or nil,
|
|
port = data.server_port ~= nil and tonumber(data.server_port) or nil,
|
|
skipFallback = data.server_skipfallback == "true" and true or false,
|
|
domains = next(domains) and domains or nil,
|
|
expectIPs = next(expectedips) and expectedips or nil,
|
|
clientIP = data.server_clientip
|
|
}
|
|
|
|
table.insert(servers, server)
|
|
end)
|
|
|
|
for i, v in ipairs(dns.alt_dns == nil and {} or dns.alt_dns) do
|
|
table.insert(servers, v)
|
|
end
|
|
|
|
return {
|
|
tag = dns.tag,
|
|
clientIP = dns.clientip,
|
|
queryStrategy = dns.querystrategy,
|
|
disableCache = dns.disablecache == "true" and true or false,
|
|
disableFallback = dns.disablefallback == "true" and true or false,
|
|
disableFallbackIfMatch = dns.disablefallbackifmatch == "true" and true or false,
|
|
hosts = next(hosts) and hosts or nil,
|
|
servers = next(servers) and servers or nil
|
|
}
|
|
end
|
|
|
|
local function routings()
|
|
local rules = {}
|
|
|
|
ucursor:foreach("xjay", "routing_rule", function(data)
|
|
local domain = {}
|
|
local ip = {}
|
|
local port = {}
|
|
local source = {}
|
|
local sourceport = {}
|
|
local network = {}
|
|
local protocol = {}
|
|
local inboundtag = {}
|
|
|
|
for i, v in ipairs(data.rule_domain == nil and {} or data.rule_domain) do
|
|
table.insert(domain, v)
|
|
end
|
|
|
|
for i, v in ipairs(data.rule_ip == nil and {} or data.rule_ip) do
|
|
table.insert(ip, v)
|
|
end
|
|
|
|
if data.rule_port == nil then
|
|
port = nil
|
|
elseif #data.rule_port == 1 then
|
|
port = tonumber(data.rule_port[1])
|
|
elseif #data.rule_port > 1 then
|
|
for i, v in ipairs(data.rule_port) do
|
|
port[#port+1] = tostring(i == 1 and v or "," .. v)
|
|
end
|
|
port = table.concat(port)
|
|
end
|
|
|
|
for i, v in ipairs(data.rule_source == nil and {} or data.rule_source) do
|
|
table.insert(source, v)
|
|
end
|
|
|
|
if data.rule_sourceport == nil then
|
|
sourceport = nil
|
|
elseif #data.rule_sourceport == 1 then
|
|
sourceport = tonumber(data.rule_sourceport[1])
|
|
elseif #data.rule_sourceport > 1 then
|
|
for i, v in ipairs(data.rule_sourceport) do
|
|
sourceport[#sourceport+1] = tostring(i == 1 and v or "," .. v)
|
|
end
|
|
sourceport = table.concat(sourceport)
|
|
end
|
|
|
|
for i, v in ipairs(data.rule_network == nil and {} or data.rule_network) do
|
|
if i > 1 then v = "," .. v end
|
|
network[#network+1] = tostring(v)
|
|
end
|
|
|
|
for i, v in ipairs(data.rule_protocol == nil and {} or data.rule_protocol) do
|
|
table.insert(protocol, v)
|
|
end
|
|
|
|
for i, v in ipairs(data.rule_inboundtag == nil and {} or data.rule_inboundtag) do
|
|
table.insert(inboundtag, v)
|
|
end
|
|
|
|
local rule = {
|
|
domainMatcher = data.domain_matcher == nil and nil or data.domain_matcher,
|
|
type = "field",
|
|
domain = next(domain) and domain or nil,
|
|
ip = next(ip) and ip or nil,
|
|
port = port,
|
|
source = next(source) and source or nil,
|
|
sourcePort = sourceport,
|
|
network = next(network) and table.concat(network) or nil,
|
|
protocol = next(protocol) and protocol or nil,
|
|
attrs = data.rule_attrs,
|
|
inboundTag = next(inboundtag) and inboundtag or nil,
|
|
outboundTag = data.rule_outboundtag
|
|
}
|
|
|
|
table.insert(rules, rule)
|
|
end)
|
|
|
|
return {
|
|
domainStrategy = routing.domainstrategy,
|
|
domainMatcher = routing.domainmatcher,
|
|
rules = rules
|
|
}
|
|
end
|
|
|
|
local xray = {
|
|
log = logging(),
|
|
inbounds = inbounds(),
|
|
outbounds = outbounds(),
|
|
dns = dnss(),
|
|
routing = routings()
|
|
}
|
|
|
|
print(json.stringify(xray, true))
|