mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-01 00:35:13 +08:00
🌴 Sync 2026-03-05 23:51:42
This commit is contained in:
1 parent
0d155c70be
commit
32600acc60
14258 files changed
+3954570
No files matched your search
Executable
BIN
Binary file not shown.
Executable
+36790
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"admin/services/xclient/overview": {
|
||||
"title": "XClient",
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "xclient"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [
|
||||
"luci-app-xclient"
|
||||
],
|
||||
"uci": {
|
||||
"xclient": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"luci-app-xclient": {
|
||||
"description": "Grant UCI access for luci-app-xclient",
|
||||
"read": {
|
||||
"uci": ["xclient"]
|
||||
},
|
||||
"write": {
|
||||
"uci": ["xclient"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"config": "xclient",
|
||||
"init": "xclient"
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
#!/bin/sh
|
||||
cur_core_version=$(/usr/bin/xray -version | awk '{print $2}' | sed -n 1P 2>&1 & >/dev/null)
|
||||
if [ "$?" -eq "0" ]; then
|
||||
rm -rf /usr/share/xclient/core_version
|
||||
if [ $cur_core_version ]; then
|
||||
echo $cur_core_version > /usr/share/xclient/core_version 2>&1 & >/dev/null
|
||||
elif [ $cur_core_version == "" ]; then
|
||||
echo "--" > /usr/share/xclient/core_version 2>&1 & >/dev/null
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
|
||||
new_core_version=`curl -sL "https://github.com/XTLS/Xray-core/tags"| grep "/XTLS/Xray-core/releases/"| head -n 1| awk -F "/tag/" '{print $2}'| sed 's/\">//'`
|
||||
if [ "$?" -eq "0" ]; then
|
||||
rm -rf /usr/share/xclient/new_core
|
||||
if [ $new_core_version ]; then
|
||||
echo $new_core_version > /usr/share/xclient/new_core 2>&1 & >/dev/null
|
||||
elif [ $new_core_version == "" ]; then
|
||||
echo 0 > /usr/share/xclient/new_core 2>&1 & >/dev/null
|
||||
fi
|
||||
fi
|
||||
@@ -0,0 +1,626 @@
|
||||
#!/usr/bin/lua
|
||||
--local ucursor = require "luci.model.uci"
|
||||
local ucursor = require "luci.model.uci".cursor()
|
||||
local json = require "luci.jsonc"
|
||||
local nixiofs = require "nixio.fs"
|
||||
|
||||
local proxy_section = ucursor:get_first("xclient", "global")
|
||||
local proxy = ucursor:get_all("xclient", proxy_section)
|
||||
|
||||
local tcp_server_section = proxy.global_server or nil
|
||||
local tcp_server = ucursor:get_all("xclient", tcp_server_section)
|
||||
|
||||
local udp_server_section = proxy.udp_relay_server or nil
|
||||
local udp_server = ucursor:get_all("xclient", udp_server_section)
|
||||
|
||||
local socks5_server_section = proxy.socks5_server or nil
|
||||
local socks5_server = ucursor:get_all("xclient", socks5_server_section)
|
||||
|
||||
local server_section = arg[1] or nil
|
||||
local node = ucursor:get_all("xclient", server_section)
|
||||
|
||||
local geoip_existence = false
|
||||
local geosite_existence = false
|
||||
|
||||
local xray_data_file_iterator = nixiofs.dir("/usr/share/xclient")
|
||||
|
||||
local proto = arg[2]
|
||||
local local_port = arg[3] or "1234"
|
||||
local socks_port = arg[4] or "1080"
|
||||
local _protocol = arg[5] or "redir"
|
||||
local _local = arg[6] or "0"
|
||||
|
||||
repeat
|
||||
local fn = xray_data_file_iterator()
|
||||
if fn == "geoip.dat" then
|
||||
geoip_existence = true
|
||||
end
|
||||
if fn == "geosite.dat" then
|
||||
geosite_existence = true
|
||||
end
|
||||
until fn == nil
|
||||
|
||||
|
||||
local function direct_outbound()
|
||||
return {
|
||||
protocol = "freedom",
|
||||
settings = {
|
||||
domainStrategy = "UseIP"
|
||||
},
|
||||
tag = "direct"
|
||||
}
|
||||
end
|
||||
|
||||
local function block_outbound()
|
||||
return {
|
||||
protocol = "blackhole",
|
||||
tag = "block",
|
||||
settings = {
|
||||
response = {
|
||||
type = "http"
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
local function dns_outbound()
|
||||
return {
|
||||
protocol = "dns",
|
||||
address = "1.1.1.1",
|
||||
network = "tcp,udp",
|
||||
port = 53,
|
||||
tag = "dns_outbound"
|
||||
}
|
||||
end
|
||||
|
||||
local function stream_tcp_fake_http_request(server)
|
||||
if server.tcp_guise == "http" then
|
||||
return {
|
||||
version = "1.1",
|
||||
method = "GET",
|
||||
path = server.http_path,
|
||||
headers = {
|
||||
Host = server.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"
|
||||
}
|
||||
}
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
local function stream_tcp_fake_http_response(server)
|
||||
if server.tcp_guise == "http" then
|
||||
return {
|
||||
version = "1.1",
|
||||
status = "200",
|
||||
reason = "OK",
|
||||
headers = {
|
||||
Content_Type = {"application/octet-stream", "video/mpeg"},
|
||||
Transfer_Encoding = {"chunked"},
|
||||
Connection = {"keep-alive"},
|
||||
Pragma = "no-cache"
|
||||
}
|
||||
}
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
local function stream_tcp(server)
|
||||
if server.transport == "tcp" then
|
||||
return {
|
||||
header = {
|
||||
type = server.tcp_guise,
|
||||
request = stream_tcp_fake_http_request(server),
|
||||
response = stream_tcp_fake_http_response(server)
|
||||
}
|
||||
}
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
local function stream_h2(server)
|
||||
if (server.transport == "h2") then
|
||||
return {
|
||||
path = server.h2_path,
|
||||
host = server.h2_host,
|
||||
read_idle_timeout = tonumber(server.read_idle_timeout) or nil,
|
||||
health_check_timeout = tonumber(server.health_check_timeout) or nil
|
||||
}
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
local function stream_grpc(server)
|
||||
if (server.transport == "grpc") then
|
||||
return {
|
||||
serviceName = server.serviceName or "",
|
||||
multiMode = server.grpc_multi_mode == "1",
|
||||
initial_windows_size = tonumber(server.initial_windows_size) or nil,
|
||||
idle_timeout = tonumber(server.idle_timeout) or nil,
|
||||
health_check_timeout = tonumber(server.health_check_timeout) or nil,
|
||||
permit_without_stream = (server.permit_without_stream == "1") and true or nil
|
||||
}
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function stream_kcp(server)
|
||||
if server.transport == "mkcp" then
|
||||
local mkcp_seed = nil
|
||||
if server.seed ~= "" then
|
||||
mkcp_seed = server.seed
|
||||
end
|
||||
return {
|
||||
mtu = tonumber(server.mtu),
|
||||
tti = tonumber(server.tti),
|
||||
uplinkCapacity = tonumber(server.uplink_capacity),
|
||||
downlinkCapacity = tonumber(server.downlink_capacity),
|
||||
congestion = (server.congestion == "1") and true or false,
|
||||
readBufferSize = tonumber(server.read_buffer_size),
|
||||
writeBufferSize = tonumber(server.write_buffer_size),
|
||||
seed = mkcp_seed,
|
||||
header = {
|
||||
type = server.kcp_guise
|
||||
}
|
||||
}
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
local function stream_quic(server)
|
||||
if server.transport == "quic" then
|
||||
return {
|
||||
security = server.quic_security,
|
||||
key = server.quic_key,
|
||||
header = {type = server.quic_guise}
|
||||
}
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
local function stream_ws(server)
|
||||
if server.transport == "ws" then
|
||||
local headers = nil
|
||||
if (server.ws_host ~= nil) then
|
||||
headers = {
|
||||
Host = server.ws_host or server.tls_host
|
||||
}
|
||||
end
|
||||
return {
|
||||
path = server.ws_path,
|
||||
headers = headers
|
||||
}
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
local function tls_settings(server)
|
||||
local result = {
|
||||
fingerprint = server.fingerprint,
|
||||
rejectUnknownSni = (server.rejectUnknownSni == "1") and true or false,
|
||||
allowInsecure = (server.insecure == "1") and true or false,
|
||||
serverName = server.tls_host,
|
||||
}
|
||||
if server.alpn ~= nil then
|
||||
local alpn = {}
|
||||
for _, x in ipairs(server.alpn) do
|
||||
table.insert(alpn, x)
|
||||
end
|
||||
result["alpn"] = alpn
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
local function reality_settings(server)
|
||||
local result = {
|
||||
fingerprint = server.fingerprint,
|
||||
shortId = server.shortId,
|
||||
publicKey = server.publicKey,
|
||||
serverName = server.tls_host,
|
||||
spiderX = server.spiderX,
|
||||
}
|
||||
return result
|
||||
end
|
||||
|
||||
local function shadowsocks_outbound(server, tag)
|
||||
return {
|
||||
protocol = "shadowsocks",
|
||||
tag = tag,
|
||||
settings = {
|
||||
servers = {
|
||||
{
|
||||
address = server.server,
|
||||
port = tonumber(server.server_port),
|
||||
password = server.password,
|
||||
method = server.encrypt_method_v2ray_ss
|
||||
}
|
||||
}
|
||||
},
|
||||
streamSettings = {
|
||||
network = server.transport,
|
||||
security = (server.security == 'tls') and "tls" or "none",
|
||||
tlsSettings = (server.security == 'tls') and "tls" and tls_settings(server) or nil,
|
||||
quicSettings = stream_quic(server),
|
||||
kcpSettings = stream_kcp(server),
|
||||
tcpSettings = stream_tcp(server) and server.protocol ~= "shadowsocks" or nil,
|
||||
wsSettings = stream_ws(server),
|
||||
grpcSettings = stream_grpc(server),
|
||||
httpSettings = stream_h2(server)
|
||||
},
|
||||
mux = (server.mux == "1" and server.security == 'tls' or server.security == 'none') and {
|
||||
enabled = true,
|
||||
concurrency = tonumber(server.concurrency)
|
||||
} or nil
|
||||
}
|
||||
end
|
||||
|
||||
local function vmess_outbound(server, tag)
|
||||
return {
|
||||
protocol = "vmess",
|
||||
tag = tag,
|
||||
settings = {
|
||||
vnext = {
|
||||
{
|
||||
address = server.server,
|
||||
port = tonumber(server.server_port),
|
||||
users = {
|
||||
{
|
||||
id = server.vmess_id,
|
||||
alterId = 0,
|
||||
security = server.vmess_encryption
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
streamSettings = {
|
||||
network = server.transport,
|
||||
security = (server.security == 'tls') and "tls" or "none",
|
||||
tlsSettings = (server.security == 'tls') and "tls" and tls_settings(server) or nil,
|
||||
tcpSettings = stream_tcp(server),
|
||||
wsSettings = stream_ws(server),
|
||||
grpcSettings = stream_grpc(server),
|
||||
httpSettings = stream_h2(server),
|
||||
quicSettings = stream_quic(server),
|
||||
kcpSettings = stream_kcp(server),
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
local function vless_outbound(server, tag)
|
||||
local flow = server.flow
|
||||
if server.flow == "none" then
|
||||
flow = nil
|
||||
end
|
||||
return {
|
||||
protocol = "vless",
|
||||
tag = tag,
|
||||
settings = {
|
||||
vnext = {
|
||||
{
|
||||
address = server.server,
|
||||
port = tonumber(server.server_port),
|
||||
users = {
|
||||
{
|
||||
id = server.vmess_id,
|
||||
flow = flow,
|
||||
encryption = server.vless_encryption
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
streamSettings = {
|
||||
network = server.transport,
|
||||
security = (server.security == 'reality') and "reality" or (server.security == 'tls') and "tls" or "none",
|
||||
tlsSettings = (server.security == 'tls') and "tls" and tls_settings(server) or nil,
|
||||
realitySettings = (server.security == 'reality') and "reality" and reality_settings(server) or nil,
|
||||
tcpSettings = stream_tcp(server),
|
||||
wsSettings = stream_ws(server),
|
||||
grpcSettings = stream_grpc(server),
|
||||
httpSettings = stream_h2(server),
|
||||
quicSettings = stream_quic(server),
|
||||
kcpSettings = stream_kcp(server),
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
local function trojan_outbound(server, tag)
|
||||
local flow = server.flow
|
||||
if server.flow == "none" then
|
||||
flow = nil
|
||||
end
|
||||
return {
|
||||
protocol = "trojan",
|
||||
tag = tag,
|
||||
settings = {
|
||||
servers = {
|
||||
{
|
||||
address = server.server,
|
||||
port = tonumber(server.server_port),
|
||||
password = server.password,
|
||||
flow = flow,
|
||||
}
|
||||
}
|
||||
},
|
||||
streamSettings = {
|
||||
network = server.transport,
|
||||
security = (server.security == 'tls') and "tls" or "none",
|
||||
tlsSettings = (server.security == 'tls') and "tls" and tls_settings(server) or nil,
|
||||
tcpSettings = stream_tcp(server) and server.protocol ~= "trojan" or nil,
|
||||
wsSettings = stream_ws(server),
|
||||
grpcSettings = stream_grpc(server),
|
||||
httpSettings = stream_h2(server),
|
||||
quicSettings = stream_quic(server),
|
||||
kcpSettings = stream_kcp(server),
|
||||
},
|
||||
mux = (server.mux == "1" and server.security == 'tls' or server.security == 'none') and {
|
||||
enabled = true,
|
||||
concurrency = tonumber(server.concurrency)
|
||||
} or nil
|
||||
}
|
||||
end
|
||||
|
||||
local function server_outbound(server, tag)
|
||||
if server.protocol == "vmess" then
|
||||
return vmess_outbound(server, tag)
|
||||
end
|
||||
if server.protocol == "vless" then
|
||||
return vless_outbound(server, tag)
|
||||
end
|
||||
if server.protocol == "shadowsocks" then
|
||||
return shadowsocks_outbound(server, tag)
|
||||
end
|
||||
if server.protocol == "trojan" then
|
||||
return trojan_outbound(server, tag)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
local function proxy_inbound()
|
||||
if local_port == "0" then
|
||||
return nil
|
||||
end
|
||||
|
||||
return {
|
||||
port = local_port,
|
||||
protocol = "dokodemo-door",
|
||||
tag = "proxy_inbound",
|
||||
sniffing = {
|
||||
enabled = true,
|
||||
destOverride = {"http", "tls"}
|
||||
} ,
|
||||
settings = {
|
||||
network = proto,
|
||||
followRedirect = true
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
local function socks_inbound()
|
||||
if socks_port == "0" then
|
||||
return nil
|
||||
end
|
||||
return {
|
||||
port = socks_port,
|
||||
protocol = "socks",
|
||||
tag = "socks_inbound",
|
||||
settings = {
|
||||
auth = "noauth",
|
||||
udp = true
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
local function dns_inbounds()
|
||||
local servers = {}
|
||||
if proxy["dns_servers"] ~= nil then
|
||||
for _, x in ipairs(proxy["dns_servers"]) do
|
||||
table.insert(servers, x)
|
||||
end
|
||||
end
|
||||
local i = 0
|
||||
ucursor:foreach("xclient", "dns", function(v)
|
||||
i = i + 1
|
||||
table.insert(servers, {
|
||||
address = v.address or nil,
|
||||
port = v.port,
|
||||
domains = v.domains or nil,
|
||||
expectIPs = v.expectIPs or nil,
|
||||
skipFallback = v.skipFallback == "1" and true or false
|
||||
})
|
||||
end)
|
||||
return servers
|
||||
end
|
||||
|
||||
local function dns()
|
||||
local result = {}
|
||||
for _, v in ipairs(dns_inbounds()) do
|
||||
table.insert(result, v)
|
||||
end
|
||||
return {
|
||||
server = result,
|
||||
clientIp = proxy.clientIp or nil,
|
||||
queryStrategy = proxy.queryStrategy,
|
||||
disableCache = proxy.disableCache == "1" and true or false,
|
||||
disableFallback = proxy.disableFallback == "1" and true or false,
|
||||
disableFallbackIfMatch = proxy.disableFallbackIfMatch == "1" and true or false,
|
||||
tag = "dns_inbound"
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
local function inbounds()
|
||||
local i = {}
|
||||
if proxy.global_server ~= "nil" or proxy.udp_server ~= "nil" then
|
||||
i = {
|
||||
proxy_inbound(),
|
||||
}
|
||||
if proxy.socks5_server == "same" or _local == "1" then
|
||||
table.insert(i, socks_inbound())
|
||||
end
|
||||
table.insert(i, {
|
||||
listen = "127.0.0.1",
|
||||
port = 8888,
|
||||
protocol = "dokodemo-door",
|
||||
settings = {
|
||||
address = "127.0.0.1"
|
||||
},
|
||||
tag = "api"
|
||||
})
|
||||
return i
|
||||
end
|
||||
end
|
||||
|
||||
local function api()
|
||||
return {
|
||||
tag = "api",
|
||||
services = {
|
||||
"StatsService"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
local function routing()
|
||||
local rule = {}
|
||||
local i = 0
|
||||
ucursor:foreach("xclient", "rule", function(v)
|
||||
i = i + 1
|
||||
table.insert(rule, {
|
||||
type = "field",
|
||||
domainMatcher = v.domainMatcher or nil,
|
||||
domain = v.domain or nil,
|
||||
ip = v.ip or nil,
|
||||
port = v.port or nil,
|
||||
sourcePort = v.sourcePort or nil,
|
||||
network = v.network or nil,
|
||||
source = v.source or nil,
|
||||
inboundTag = v.inboundTag or nil,
|
||||
protocol = v.protocol or nil,
|
||||
outboundTag = v.outboundTag or nil,
|
||||
})
|
||||
end)
|
||||
table.insert(rule, {
|
||||
type = "field",
|
||||
inboundTag = {
|
||||
"api"
|
||||
},
|
||||
outboundTag = "api",
|
||||
enabled = true
|
||||
})
|
||||
return rule
|
||||
end
|
||||
|
||||
|
||||
local function routing_rules()
|
||||
local result = {}
|
||||
for _, v in ipairs(routing()) do
|
||||
table.insert(result, v)
|
||||
end
|
||||
return {
|
||||
domainMatcher = proxy.domainMatcher,
|
||||
domainStrategy = proxy.routing_strategy or 'AsIs',
|
||||
rules = result
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
local function outbounds()
|
||||
local result = {}
|
||||
if arg[7] == "none" then
|
||||
result = {
|
||||
server_outbound(socks5_server, "proxy_outbound"),
|
||||
dns_outbound(),
|
||||
direct_outbound(),
|
||||
block_outbound()
|
||||
}
|
||||
elseif proto == "tcp" then
|
||||
result = {
|
||||
server_outbound(tcp_server, "proxy_outbound"),
|
||||
dns_outbound(),
|
||||
direct_outbound(),
|
||||
block_outbound()
|
||||
}
|
||||
elseif proto == "udp" then
|
||||
result = {
|
||||
server_outbound(udp_server, "proxy_outbound"),
|
||||
dns_outbound(),
|
||||
direct_outbound(),
|
||||
block_outbound()
|
||||
}
|
||||
elseif proto == "tcp,udp" then
|
||||
result = {
|
||||
server_outbound(tcp_server, "proxy_outbound"),
|
||||
dns_outbound(),
|
||||
direct_outbound(),
|
||||
block_outbound()
|
||||
}
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
local function logging()
|
||||
return {
|
||||
loglevel = proxy.log_level or "warning",
|
||||
}
|
||||
end
|
||||
|
||||
local function policy()
|
||||
return {
|
||||
system = {
|
||||
statsOutboundUplink = true,
|
||||
statsOutboundDownlink = true
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
local xclient = {
|
||||
log = logging(),
|
||||
dns = dns(),
|
||||
routing = routing_rules(),
|
||||
dns = dns(),
|
||||
api = api(),
|
||||
policy = policy(),
|
||||
routing = routing_rules(),
|
||||
inbounds = inbounds(),
|
||||
outbounds = outbounds()
|
||||
}
|
||||
|
||||
|
||||
local shadowsocks_plugin = {
|
||||
server = node.server,
|
||||
server_port = tonumber(node.server_port),
|
||||
local_address = "0.0.0.0",
|
||||
local_port = tonumber(local_port),
|
||||
mode = (proto == "tcp,udp") and "tcp_and_udp" or proto .. "_only",
|
||||
password = node.password,
|
||||
method = node.encrypt_method_v2ray_ss,
|
||||
reuse_port = true,
|
||||
protocol = _protocol,
|
||||
plugin = node.plugin,
|
||||
plugin_opts = node.plugin_opts or nil
|
||||
}
|
||||
|
||||
if node.protocol == "shadowsocks-plugin" then
|
||||
print(json.stringify(shadowsocks_plugin, true))
|
||||
else
|
||||
print(json.stringify(xclient, true))
|
||||
end
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
#!/bin/sh
|
||||
echo '' >/var/lock/xclient.lock 2>/dev/null
|
||||
echo '' >/tmp/geo_ip_update.txt 2>/dev/null
|
||||
echo 'Checking for latest version' >/tmp/geo_ip_update.txt 2>/dev/null
|
||||
|
||||
new_version=`wget -qO- "https://github.com/Loyalsoldier/v2ray-rules-dat/tags"| grep "/Loyalsoldier/v2ray-rules-dat/releases/"| head -n 1| awk -F "/tag/" '{print $2}'| sed 's/\">//'`
|
||||
|
||||
rm -rf /tmp/geoip.dat >/dev/null 2>&1
|
||||
echo 'Latest version: ' $new_version >/tmp/geo_ip_update.txt 2>/dev/null
|
||||
|
||||
if [ -f /var/run/geo_ip_down_complete ];then
|
||||
rm -rf /var/run/geo_ip_down_complete 2>/dev/null
|
||||
fi
|
||||
|
||||
echo 'Starting download...' >/tmp/geo_ip_update.txt 2>/dev/null
|
||||
|
||||
wget -c4 --no-check-certificate --timeout=60 --user-agent="Mozilla" https://github.com/Loyalsoldier/v2ray-rules-dat/releases/download/"$new_version"/geoip.dat -O /tmp/geoip.dat
|
||||
sleep 2
|
||||
if [ "$?" -eq "0" ] && [ "$(ls -l /tmp/geoip.dat |awk '{print int($5)}')" -ne 0 ]; then
|
||||
touch /var/run/geo_ip_down_complete >/dev/null 2>&1
|
||||
rm -rf /var/run/geo_ip_update >/dev/null 2>&1
|
||||
rm -rf /usr/bin/geoip.dat && mv /tmp/geoip.dat /usr/bin
|
||||
echo $new_version > /usr/share/xclient/geoip_version 2>/dev/null
|
||||
echo "" > /tmp/geo_ip_update.txt >/dev/null 2>&1
|
||||
rm -rf /var/lock/xclient.lock 2>/dev/null
|
||||
if pidof xclient >/dev/null; then
|
||||
/etc/init.d/xclient boot 2>/dev/null
|
||||
fi
|
||||
fi
|
||||
@@ -0,0 +1 @@
|
||||
202201242210
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
#!/bin/sh
|
||||
echo '' >/var/lock/xclient.lock 2>/dev/null
|
||||
echo '' >/tmp/geo_site_update.txt 2>/dev/null
|
||||
echo 'Checking for latest version' >/tmp/geo_site_update.txt 2>/dev/null
|
||||
|
||||
new_version=`wget -qO- "https://github.com/Loyalsoldier/v2ray-rules-dat/tags"| grep "/Loyalsoldier/v2ray-rules-dat/releases/"| head -n 1| awk -F "/tag/" '{print $2}'| sed 's/\">//'`
|
||||
|
||||
rm -rf /tmp/geosite.dat >/dev/null 2>&1
|
||||
|
||||
echo 'Latest version: ' $new_version >/tmp/geo_site_update.txt 2>/dev/null
|
||||
|
||||
if [ -f /var/run/geo_site_down_complete ];then
|
||||
rm -rf /var/run/geo_site_down_complete 2>/dev/null
|
||||
fi
|
||||
|
||||
echo 'Starting download...' >/tmp/geo_ip_update.txt 2>/dev/null
|
||||
|
||||
wget -c4 --no-check-certificate --timeout=60 --user-agent="Mozilla" https://github.com/Loyalsoldier/v2ray-rules-dat/releases/download/"$new_version"/geosite.dat -O /tmp/geosite.dat
|
||||
if [ "$?" -eq "0" ] && [ "$(ls -l /tmp/geosite.dat |awk '{print int($5)}')" -ne 0 ]; then
|
||||
touch /var/run/geo_site_down_complete >/dev/null 2>&1
|
||||
rm -rf /var/run/geo_site_update >/dev/null 2>&1
|
||||
rm -rf /usr/bin/geosite.dat && mv /tmp/geosite.dat /usr/bin
|
||||
echo $new_version > /usr/share/xclient/geosite_version 2>/dev/null
|
||||
echo "" > /tmp/geo_site_update.txt >/dev/null 2>&1
|
||||
rm -rf /var/lock/xclient.lock 2>/dev/null
|
||||
if pidof xclient >/dev/null; then
|
||||
/etc/init.d/xclient boot 2>/dev/null
|
||||
fi
|
||||
fi
|
||||
@@ -0,0 +1 @@
|
||||
202201242210
|
||||
+251
@@ -0,0 +1,251 @@
|
||||
#!/bin/sh
|
||||
|
||||
TAG="_XCLIENT_SPEC_RULE_" # comment tag
|
||||
IPT="iptables -t nat" # alias of iptables
|
||||
FWI=$(uci get firewall.xclient.path 2>/dev/null) # firewall include file
|
||||
usage() {
|
||||
cat <<-EOF
|
||||
Usage: xrules [options]
|
||||
|
||||
Valid options are:
|
||||
|
||||
-s <server_ip> ip address of xclient remote server
|
||||
-l <local_port> port number of xclient local server
|
||||
-S <server_ip> ip address of xclient remote UDP server
|
||||
-L <local_port> port number of xclient local UDP server
|
||||
define access control mode
|
||||
-b <wan_ips> wan ip of will be bypassed
|
||||
-B <bp_lan_ips> lan ip of will be bypassed proxy
|
||||
-p <fp_lan_ips> lan ip of will be global proxy
|
||||
-m <Interface> Interface name
|
||||
-e <extra_options> extra options for iptables
|
||||
-u enable udprelay mode, TPROXY is required
|
||||
-U enable udprelay mode, using different IP
|
||||
and ports for TCP and UDP
|
||||
-f flush the rules
|
||||
-h show this help message and exit
|
||||
EOF
|
||||
exit $1
|
||||
}
|
||||
|
||||
loger() {
|
||||
# 1.alert 2.crit 3.err 4.warn 5.notice 6.info 7.debug
|
||||
logger -st xrules[$$] -p$1 $2
|
||||
}
|
||||
|
||||
flush_r() {
|
||||
flush_iptables() {
|
||||
local ipt="iptables -t $1"
|
||||
local DAT=$(iptables-save -t $1)
|
||||
eval $(echo "$DAT" | grep "$TAG" | sed -e 's/^-A/$ipt -D/' -e 's/$/;/')
|
||||
for chain in $(echo "$DAT" | awk '/^:XCLIENT_SPEC/{print $1}'); do
|
||||
$ipt -F ${chain:1} 2>/dev/null && $ipt -X ${chain:1}
|
||||
done
|
||||
}
|
||||
flush_iptables nat
|
||||
flush_iptables mangle
|
||||
ip rule del fwmark 0x01/0x01 table 100 2>/dev/null
|
||||
ip route del local 0.0.0.0/0 dev lo table 100 2>/dev/null
|
||||
ipset -X xclient_spec_wan_ac 2>/dev/null
|
||||
ipset -X fplan 2>/dev/null
|
||||
ipset -X bplan 2>/dev/null
|
||||
ipset -X blacklist 2>/dev/null
|
||||
[ -n "$FWI" ] && echo '#!/bin/sh' >$FWI
|
||||
return 0
|
||||
}
|
||||
|
||||
ipset_r() {
|
||||
ipset -! -R <<-EOF || return 1
|
||||
create xclient_spec_wan_ac hash:net
|
||||
$(gen_spec_iplist | sed -e "s/^/add xclient_spec_wan_ac /")
|
||||
EOF
|
||||
$IPT -N XCLIENT_SPEC_WAN_AC
|
||||
$IPT -I XCLIENT_SPEC_WAN_AC -p tcp ! --dport 53 -d $server -j RETURN
|
||||
$IPT -A XCLIENT_SPEC_WAN_AC -m set --match-set xclient_spec_wan_ac dst -j RETURN
|
||||
$IPT -A XCLIENT_SPEC_WAN_AC -j XCLIENT_SPEC_WAN_FW
|
||||
ipset -N fplan hash:net 2>/dev/null
|
||||
for ip in $LAN_FP_IP; do ipset -! add fplan $ip; done
|
||||
$IPT -I XCLIENT_SPEC_WAN_AC -m set --match-set fplan src -j XCLIENT_SPEC_WAN_FW
|
||||
ipset -N bplan hash:net 2>/dev/null
|
||||
for ip in $LAN_BP_IP; do ipset -! add bplan $ip; done
|
||||
$IPT -I XCLIENT_SPEC_WAN_AC -m set --match-set bplan src -j RETURN
|
||||
ipset -N blacklist hash:net 2>/dev/null
|
||||
$IPT -I XCLIENT_SPEC_WAN_AC -m set --match-set blacklist dst -j RETURN
|
||||
if [ $(ipset list music -name -quiet | grep music) ]; then
|
||||
$IPT -I XCLIENT_SPEC_WAN_AC -m set --match-set music dst -j RETURN 2>/dev/null
|
||||
fi
|
||||
for ip in $WAN_BP_IP; do ipset -! add blacklist $ip; done
|
||||
return $?
|
||||
}
|
||||
|
||||
fw_rule() {
|
||||
$IPT -N XCLIENT_SPEC_WAN_FW
|
||||
$IPT -A XCLIENT_SPEC_WAN_FW -d 0.0.0.0/8 -j RETURN
|
||||
$IPT -A XCLIENT_SPEC_WAN_FW -d 10.0.0.0/8 -j RETURN
|
||||
$IPT -A XCLIENT_SPEC_WAN_FW -d 127.0.0.0/8 -j RETURN
|
||||
$IPT -A XCLIENT_SPEC_WAN_FW -d 169.254.0.0/16 -j RETURN
|
||||
$IPT -A XCLIENT_SPEC_WAN_FW -d 172.16.0.0/12 -j RETURN
|
||||
$IPT -A XCLIENT_SPEC_WAN_FW -d 192.168.0.0/16 -j RETURN
|
||||
$IPT -A XCLIENT_SPEC_WAN_FW -d 224.0.0.0/4 -j RETURN
|
||||
$IPT -A XCLIENT_SPEC_WAN_FW -d 240.0.0.0/4 -j RETURN
|
||||
$IPT -A XCLIENT_SPEC_WAN_FW -p tcp -j REDIRECT --to-ports $local_port 2>/dev/null || {
|
||||
loger 3 "Can't redirect, please check the iptables."
|
||||
exit 1
|
||||
}
|
||||
return $?
|
||||
}
|
||||
|
||||
ac_rule() {
|
||||
if [ -z "$Interface" ]; then
|
||||
$IPT -I PREROUTING 1 -p tcp $EXT_ARGS -m comment --comment "$TAG" -j XCLIENT_SPEC_WAN_AC
|
||||
else
|
||||
for name in $Interface; do
|
||||
local IFNAME=$(uci -P /var/state get network.$name.device 2>/dev/null)
|
||||
[ -n "$IFNAME" ] && $IPT -I PREROUTING 1 ${IFNAME:+-i $IFNAME} -p tcp $EXT_ARGS -m comment --comment "$TAG" -j XCLIENT_SPEC_WAN_AC
|
||||
done
|
||||
fi
|
||||
$IPT -I OUTPUT 1 -p tcp $EXT_ARGS -m comment --comment "$TAG" -j XCLIENT_SPEC_WAN_AC
|
||||
return $?
|
||||
}
|
||||
|
||||
tp_rule() {
|
||||
[ -n "$TPROXY" ] || return 0
|
||||
ip rule add fwmark 0x01/0x01 table 100
|
||||
ip route add local 0.0.0.0/0 dev lo table 100
|
||||
local ipt="iptables -t mangle"
|
||||
$ipt -N XCLIENT_SPEC_TPROXY
|
||||
$ipt -A XCLIENT_SPEC_TPROXY -p udp --dport 53 -j RETURN
|
||||
$ipt -A XCLIENT_SPEC_TPROXY -p udp -d 0.0.0.0/8 -j RETURN
|
||||
$ipt -A XCLIENT_SPEC_TPROXY -p udp -d 10.0.0.0/8 -j RETURN
|
||||
$ipt -A XCLIENT_SPEC_TPROXY -p udp -d 127.0.0.0/8 -j RETURN
|
||||
$ipt -A XCLIENT_SPEC_TPROXY -p udp -d 169.254.0.0/16 -j RETURN
|
||||
$ipt -A XCLIENT_SPEC_TPROXY -p udp -d 172.16.0.0/12 -j RETURN
|
||||
$ipt -A XCLIENT_SPEC_TPROXY -p udp -d 192.168.0.0/16 -j RETURN
|
||||
$ipt -A XCLIENT_SPEC_TPROXY -p udp -d 224.0.0.0/4 -j RETURN
|
||||
$ipt -A XCLIENT_SPEC_TPROXY -p udp -d 240.0.0.0/4 -j RETURN
|
||||
$ipt -A XCLIENT_SPEC_TPROXY -p udp ! --dport 53 -d $SERVER -j RETURN
|
||||
[ "$server" != "$SERVER" ] && ipset -! add blacklist $SERVER
|
||||
$ipt -A XCLIENT_SPEC_TPROXY -p udp -m set --match-set bplan src -j RETURN
|
||||
$ipt -A XCLIENT_SPEC_TPROXY -p udp -m set --match-set fplan src -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark 0x01/0x01
|
||||
$ipt -A XCLIENT_SPEC_TPROXY -p udp -m set --match-set xclient_spec_wan_ac dst -j RETURN
|
||||
$ipt -A XCLIENT_SPEC_TPROXY -p udp -j TPROXY --on-port "$LOCAL_PORT" --tproxy-mark 0x01/0x01
|
||||
if [ -z "$Interface" ]; then
|
||||
$ipt -I PREROUTING 1 -p udp $EXT_ARGS -m comment --comment "$TAG" -j XCLIENT_SPEC_TPROXY
|
||||
else
|
||||
for name in $Interface; do
|
||||
local IFNAME=$(uci -P /var/state get network.$name.device 2>/dev/null)
|
||||
[ -n "$IFNAME" ] && $ipt -I PREROUTING 1 ${IFNAME:+-i $IFNAME} -p udp $EXT_ARGS -m comment --comment "$TAG" -j XCLIENT_SPEC_TPROXY
|
||||
done
|
||||
fi
|
||||
return $?
|
||||
}
|
||||
|
||||
get_wan_ip() {
|
||||
cat <<-EOF | grep -E "^([0-9]{1,3}\.){3}[0-9]{1,3}"
|
||||
$server
|
||||
$SERVER
|
||||
$WAN_BP_IP
|
||||
EOF
|
||||
}
|
||||
|
||||
gen_spec_iplist() {
|
||||
cat <<-EOF
|
||||
0.0.0.0/8
|
||||
10.0.0.0/8
|
||||
100.64.0.0/10
|
||||
127.0.0.0/8
|
||||
169.254.0.0/16
|
||||
172.16.0.0/12
|
||||
192.0.0.0/24
|
||||
192.0.2.0/24
|
||||
192.88.99.0/24
|
||||
192.168.0.0/16
|
||||
198.18.0.0/15
|
||||
198.51.100.0/24
|
||||
203.0.113.0/24
|
||||
224.0.0.0/4
|
||||
240.0.0.0/4
|
||||
255.255.255.255
|
||||
$(get_wan_ip)
|
||||
EOF
|
||||
}
|
||||
|
||||
gen_include() {
|
||||
[ -n "$FWI" ] || return 0
|
||||
extract_rules() {
|
||||
echo "*$1"
|
||||
iptables-save -t $1 | grep XCLIENT_SPEC_ | sed -e "s/^-A \(OUTPUT\|PREROUTING\)/-I \1 1/"
|
||||
echo 'COMMIT'
|
||||
}
|
||||
cat <<-EOF >>$FWI
|
||||
iptables-save -c | grep -v "XCLIENT_SPEC" | iptables-restore -c
|
||||
iptables-restore -n <<-EOT
|
||||
$(extract_rules nat)
|
||||
$(extract_rules mangle)
|
||||
EOT
|
||||
EOF
|
||||
return 0
|
||||
}
|
||||
|
||||
while getopts ":m:s:l:S:L:e:B:b:p:D:uUfh" arg; do
|
||||
case "$arg" in
|
||||
m)
|
||||
Interface=$OPTARG
|
||||
;;
|
||||
s)
|
||||
server=$OPTARG
|
||||
;;
|
||||
l)
|
||||
local_port=$OPTARG
|
||||
;;
|
||||
S)
|
||||
SERVER=$OPTARG
|
||||
;;
|
||||
L)
|
||||
LOCAL_PORT=$OPTARG
|
||||
;;
|
||||
e)
|
||||
EXT_ARGS=$OPTARG
|
||||
;;
|
||||
B)
|
||||
LAN_BP_IP=$OPTARG
|
||||
;;
|
||||
b)
|
||||
WAN_BP_IP=$(for ip in $OPTARG; do echo $ip; done)
|
||||
;;
|
||||
p)
|
||||
LAN_FP_IP=$OPTARG
|
||||
;;
|
||||
u)
|
||||
TPROXY=1
|
||||
;;
|
||||
U)
|
||||
TPROXY=2
|
||||
;;
|
||||
f)
|
||||
flush_r
|
||||
exit 0
|
||||
;;
|
||||
h) usage 0 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$server" -o -z "$local_port" ]; then
|
||||
usage 2
|
||||
fi
|
||||
|
||||
case "$TPROXY" in
|
||||
1)
|
||||
SERVER=$server
|
||||
LOCAL_PORT=$local_port
|
||||
;;
|
||||
2)
|
||||
: ${SERVER:?"You must assign an ip for the udp relay server."}
|
||||
: ${LOCAL_PORT:?"You must assign a port for the udp relay server."}
|
||||
;;
|
||||
esac
|
||||
|
||||
flush_r && fw_rule && ipset_r && ac_rule && tp_rule && gen_include
|
||||
RET=$?
|
||||
[ "$RET" = 0 ] || loger 3 "Start failed!"
|
||||
exit $RET
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
#!/bin/sh
|
||||
|
||||
. $IPKG_INSTROOT/etc/init.d/xclient
|
||||
LOCK_FILE="/var/lock/monitor.lock"
|
||||
[ -f "$LOCK_FILE" ] && exit 2
|
||||
touch "$LOCK_FILE"
|
||||
redir_tcp_process=$1
|
||||
redir_udp_process=$2
|
||||
pdnsd_process=$3
|
||||
if [ -z "$pdnsd_process" ]; then
|
||||
pdnsd_process=0
|
||||
fi
|
||||
i=0
|
||||
GLOBAL_SERVER=$(uci_get_by_type global global_server)
|
||||
server=$(uci_get_by_name $GLOBAL_SERVER server)
|
||||
server_port=$(uci_get_by_name $GLOBAL_SERVER server_port)
|
||||
|
||||
|
||||
while [ "1" == "1" ]; do
|
||||
sleep 1
|
||||
#redir tcp
|
||||
if [ "$redir_tcp_process" -gt 0 ]; then
|
||||
icount=$(busybox ps -w | grep xclient-retcp | grep -v grep | wc -l)
|
||||
if [ "$icount" == 0 ]; then
|
||||
logger -t "$NAME" "xclient redir tcp error.restart!"
|
||||
echolog "xclient redir tcp error.restart!"
|
||||
/etc/init.d/xclient reload
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
#redir udp
|
||||
if [ "$redir_udp_process" -gt 0 ]; then
|
||||
icount=$(busybox ps -w | grep xclient-reudp | grep -v grep | wc -l)
|
||||
if [ "$icount" == 0 ]; then
|
||||
logger -t "$NAME" "xclient redir udp error.restart!"
|
||||
echolog "xclient redir udp error.restart!"
|
||||
/etc/init.d/xclient reload
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
#pdnsd
|
||||
if [ "$pdnsd_process" -eq 1 ]; then
|
||||
icount=$(busybox ps -w | grep $TMP_BIN_PATH/pdnsd | grep -v grep | wc -l)
|
||||
if [ "$icount" -lt "$pdnsd_process" ]; then
|
||||
logger -t "$NAME" "pdnsd tunnel error.restart!"
|
||||
echolog "pdnsd tunnel error.restart!"
|
||||
if [ -f /var/run/pdnsd.pid ]; then
|
||||
kill $(cat /var/run/pdnsd.pid) >/dev/null 2>&1
|
||||
else
|
||||
kill -9 $(ps | grep $TMP_PATH/pdnsd.conf | grep -v grep | awk '{print $1}') >/dev/null 2>&1
|
||||
fi
|
||||
ln_start_bin $(first_type pdnsd) pdnsd -c $TMP_PATH/pdnsd.conf
|
||||
fi
|
||||
fi
|
||||
#dns2socks
|
||||
if [ "$pdnsd_process" -eq 2 ]; then
|
||||
icount=$(busybox ps -w | grep -e xclient-dns -e "dns2socks 127.0.0.1 $tmp_dns_port" | grep -v grep | wc -l)
|
||||
if [ "$icount" -lt 2 ]; then
|
||||
logger -t "$NAME" "dns2socks $dnsstr tunnel error.restart!"
|
||||
echolog "dns2socks $dnsstr tunnel error.restart!"
|
||||
dnsstr=$(uci_get_by_type global tunnel_forward 8.8.4.4:53)
|
||||
dnsserver=$(echo "$dnsstr" | awk -F ':' '{print $1}')
|
||||
dnsport=$(echo "$dnsstr" | awk -F ':' '{print $2}')
|
||||
kill -9 $(busybox ps -w | grep xclient-dns | grep -v grep | awk '{print $1}') >/dev/null 2>&1
|
||||
kill -9 $(busybox ps -w | grep "dns2socks 127.0.0.1 $tmp_dns_port" | grep -v grep | awk '{print $1}') >/dev/null 2>&1
|
||||
ln_start_bin $(first_type microsocks) microsocks -i 127.0.0.1 -p $tmp_dns_port xclient-dns
|
||||
ln_start_bin $(first_type dns2socks) dns2socks 127.0.0.1:$tmp_dns_port $dnsserver:$dnsport 127.0.0.1:$dns_port -q
|
||||
fi
|
||||
fi
|
||||
done
|
||||
@@ -0,0 +1,544 @@
|
||||
#!/usr/bin/lua
|
||||
|
||||
require "luci.model.uci"
|
||||
require "nixio"
|
||||
require "luci.util"
|
||||
require "luci.sys"
|
||||
require "luci.jsonc"
|
||||
-- these global functions are accessed all the time by the event handler
|
||||
-- so caching them is worth the effort
|
||||
|
||||
|
||||
local tinsert = table.insert
|
||||
local ssub, slen, schar, sbyte, sformat, sgsub = string.sub, string.len, string.char, string.byte, string.format, string.gsub
|
||||
local jsonParse, jsonStringify = luci.jsonc.parse, luci.jsonc.stringify
|
||||
local b64decode = nixio.bin.b64decode
|
||||
local cache = {}
|
||||
local nodeResult = setmetatable({}, {__index = cache}) -- update result
|
||||
local name = 'xclient'
|
||||
local uciType = 'servers'
|
||||
local ucic = luci.model.uci.cursor()
|
||||
local proxy = '0'
|
||||
local switch = '0'
|
||||
local subscribe_url = ucic:get(name, 'config', 'subscribe_url')
|
||||
local filter_words = '过期时间/剩余流量'
|
||||
local save_words = ''
|
||||
local v2_ss = "xray"
|
||||
local v2_tj = "xray"
|
||||
local log = function(...)
|
||||
print(os.date("%Y-%m-%d %H:%M:%S ") .. table.concat({...}, " "))
|
||||
end
|
||||
local encrypt_methods_ss = {
|
||||
"aes-128-gcm",
|
||||
"aes-192-gcm",
|
||||
"aes-256-gcm",
|
||||
"chacha20-ietf-poly1305",
|
||||
"xchacha20-ietf-poly1305"
|
||||
}
|
||||
|
||||
local function split(full, sep)
|
||||
full = full:gsub("%z", "")
|
||||
local off, result = 1, {}
|
||||
while true do
|
||||
local nStart, nEnd = full:find(sep, off)
|
||||
if not nEnd then
|
||||
local res = ssub(full, off, slen(full))
|
||||
if #res > 0 then
|
||||
tinsert(result, res)
|
||||
end
|
||||
break
|
||||
else
|
||||
tinsert(result, ssub(full, off, nStart - 1))
|
||||
off = nEnd + 1
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
-- urlencode
|
||||
local function get_urlencode(c)
|
||||
return sformat("%%%02X", sbyte(c))
|
||||
end
|
||||
|
||||
local function urlEncode(szText)
|
||||
local str = szText:gsub("([^0-9a-zA-Z ])", get_urlencode)
|
||||
str = str:gsub(" ", "+")
|
||||
return str
|
||||
end
|
||||
|
||||
local function get_urldecode(h)
|
||||
return schar(tonumber(h, 16))
|
||||
end
|
||||
local function UrlDecode(szText)
|
||||
return szText:gsub("+", " "):gsub("%%(%x%x)", get_urldecode)
|
||||
end
|
||||
|
||||
-- trim
|
||||
local function trim(text)
|
||||
if not text or text == "" then
|
||||
return ""
|
||||
end
|
||||
return (sgsub(text, "^%s*(.-)%s*$", "%1"))
|
||||
end
|
||||
-- md5
|
||||
local function md5(content)
|
||||
local stdout = luci.sys.exec('echo \"' .. urlEncode(content) .. '\" | md5sum | cut -d \" \" -f1')
|
||||
-- assert(nixio.errno() == 0)
|
||||
return trim(stdout)
|
||||
end
|
||||
-- base64
|
||||
local function base64Decode(text)
|
||||
local raw = text
|
||||
if not text then
|
||||
return ''
|
||||
end
|
||||
text = text:gsub("%z", "")
|
||||
text = text:gsub("_", "/")
|
||||
text = text:gsub("-", "+")
|
||||
local mod4 = #text % 4
|
||||
text = text .. string.sub('====', mod4 + 1)
|
||||
local result = b64decode(text)
|
||||
if result then
|
||||
return result:gsub("%z", "")
|
||||
else
|
||||
return raw
|
||||
end
|
||||
end
|
||||
|
||||
local function checkTabValue(tab)
|
||||
local revtab = {}
|
||||
for k,v in pairs(tab) do
|
||||
revtab[v] = true
|
||||
end
|
||||
return revtab
|
||||
end
|
||||
|
||||
local function processData(szType, content)
|
||||
local result = {type = szType}
|
||||
if szType == 'vmess' then
|
||||
local info = jsonParse(content)
|
||||
result.type = 'xray'
|
||||
result.protocol = 'vmess'
|
||||
result.server = info.add
|
||||
result.server_port = info.port
|
||||
result.transport = info.net
|
||||
result.vmess_id = info.id
|
||||
result.alias = info.ps
|
||||
-- result.mux = 1
|
||||
-- result.concurrency = 8
|
||||
if info.net == 'ws' then
|
||||
result.ws_host = info.host
|
||||
result.ws_path = info.path
|
||||
end
|
||||
if info.net == 'h2' then
|
||||
result.h2_host = info.host
|
||||
result.h2_path = info.path
|
||||
end
|
||||
if info.net == 'quic' then
|
||||
result.quic_guise = "none"
|
||||
result.quic_key = ""
|
||||
result.quic_security = "none"
|
||||
end
|
||||
if info.net == 'grpc' then
|
||||
result.serviceName = info.serviceName
|
||||
end
|
||||
if info.net == 'tcp' then
|
||||
if info.type and info.type ~= "http" then
|
||||
info.type = "none"
|
||||
end
|
||||
result.tcp_guise = info.type
|
||||
result.http_host = info.host
|
||||
result.http_path = info.path
|
||||
end
|
||||
if info.net == 'kcp' then
|
||||
result.kcp_guise = info.type
|
||||
result.mtu = 1350
|
||||
result.tti = 50
|
||||
result.uplink_capacity = 5
|
||||
result.downlink_capacity = 20
|
||||
result.read_buffer_size = 2
|
||||
result.write_buffer_size = 2
|
||||
end
|
||||
|
||||
if info.security then
|
||||
result.security = info.security
|
||||
end
|
||||
if info.tls == "tls" or info.tls == "1" then
|
||||
result.security = "tls"
|
||||
result.tls_host = info.sni
|
||||
result.insecure = 1
|
||||
else
|
||||
result.security = "none"
|
||||
end
|
||||
elseif szType == "ss" then
|
||||
local idx_sp = 0
|
||||
local alias = ""
|
||||
if content:find("#") then
|
||||
idx_sp = content:find("#")
|
||||
alias = content:sub(idx_sp + 1, -1)
|
||||
end
|
||||
local info = content:sub(1, idx_sp - 1)
|
||||
local hostInfo = split(base64Decode(info), "@")
|
||||
local host = split(hostInfo[2], ":")
|
||||
local userinfo = base64Decode(hostInfo[1])
|
||||
local method = userinfo:sub(1, userinfo:find(":") - 1)
|
||||
local password = userinfo:sub(userinfo:find(":") + 1, #userinfo)
|
||||
result.alias = UrlDecode(alias)
|
||||
result.type = v2_ss
|
||||
result.password = password
|
||||
result.server = host[1]
|
||||
result.transport = "tcp"
|
||||
result.protocol = "shadowsocks"
|
||||
if host[2]:find("/%?") then
|
||||
local query = split(host[2], "/%?")
|
||||
result.server_port = query[1]
|
||||
local params = {}
|
||||
for _, v in pairs(split(query[2], '&')) do
|
||||
local t = split(v, '=')
|
||||
params[t[1]] = t[2]
|
||||
end
|
||||
if params.plugin then
|
||||
local plugin_info = UrlDecode(params.plugin)
|
||||
local idx_pn = plugin_info:find(";")
|
||||
if idx_pn then
|
||||
result.plugin = plugin_info:sub(1, idx_pn - 1)
|
||||
result.plugin_opts = plugin_info:sub(idx_pn + 1, #plugin_info)
|
||||
else
|
||||
result.plugin = plugin_info
|
||||
end
|
||||
result.protocol = "shadowsocks-plugin"
|
||||
end
|
||||
else
|
||||
result.server_port = host[2]:gsub("/","")
|
||||
end
|
||||
result.encrypt_method_v2ray_ss = method
|
||||
elseif szType == "trojan" then
|
||||
local idx_sp = 0
|
||||
local alias = ""
|
||||
if content:find("#") then
|
||||
idx_sp = content:find("#")
|
||||
alias = content:sub(idx_sp + 1, -1)
|
||||
end
|
||||
local info = content:sub(1, idx_sp - 1)
|
||||
local hostInfo = split(info, "@")
|
||||
local host = split(hostInfo[2], ":")
|
||||
local userinfo = hostInfo[1]
|
||||
local password = userinfo
|
||||
result.alias = UrlDecode(alias)
|
||||
result.type = v2_tj
|
||||
result.protocol = "trojan"
|
||||
result.server = host[1]
|
||||
if host[2]:find("?") then
|
||||
local query = split(host[2], "?")
|
||||
result.server_port = query[1]
|
||||
local params = {}
|
||||
for _, v in pairs(split(query[2], '&')) do
|
||||
local t = split(v, '=')
|
||||
params[t[1]] = t[2]
|
||||
end
|
||||
|
||||
if params.obfs == "grpc" and params.security == "none" then
|
||||
result.security = "none"
|
||||
else
|
||||
result.security = "tls"
|
||||
end
|
||||
|
||||
if params.sni then
|
||||
result.tls_host = params.sni
|
||||
end
|
||||
if params.allowInsecure then
|
||||
result.insecure = params.allowInsecure
|
||||
end
|
||||
if params.obfs then
|
||||
if params.obfs == "grpc" then
|
||||
result.transport = params.obfs
|
||||
result.serviceName = params.serviceName
|
||||
end
|
||||
if params.obfs == "websocket" then
|
||||
result.transport = "ws"
|
||||
result.ws_host = params.obfsParam
|
||||
result.ws_path = params.path
|
||||
end
|
||||
else
|
||||
result.transport = "tcp"
|
||||
end
|
||||
else
|
||||
result.server_port = host[2]
|
||||
end
|
||||
result.password = password
|
||||
elseif szType == "vless" then
|
||||
local idx_sp = 0
|
||||
local alias = ""
|
||||
if content:find("#") then
|
||||
idx_sp = content:find("#")
|
||||
alias = content:sub(idx_sp + 1, -1)
|
||||
end
|
||||
local info = content:sub(1, idx_sp - 1)
|
||||
local hostInfo = split(info, "@")
|
||||
local host = split(hostInfo[2], ":")
|
||||
local uuid = hostInfo[1]
|
||||
if host[2]:find("?") then
|
||||
local query = split(host[2], "?")
|
||||
local params = {}
|
||||
for _, v in pairs(split(UrlDecode(query[2]), '&')) do
|
||||
local t = split(v, '=')
|
||||
params[t[1]] = t[2]
|
||||
end
|
||||
result.alias = UrlDecode(alias)
|
||||
result.type = 'v2ray'
|
||||
result.protocol = 'vless'
|
||||
result.server = host[1]
|
||||
result.server_port = query[1]
|
||||
result.vmess_id = uuid
|
||||
result.vless_encryption = params.encryption or "none"
|
||||
result.transport = params.type and (params.type == 'http' and 'h2' or params.type) or "tcp"
|
||||
if not params.type or params.type == "tcp" then
|
||||
result.vless_flow = params.flow
|
||||
end
|
||||
if params.type == 'ws' then
|
||||
result.ws_host = params.host
|
||||
result.ws_path = params.path or "/"
|
||||
end
|
||||
if params.type == 'http' then
|
||||
result.h2_host = params.host
|
||||
result.h2_path = params.path or "/"
|
||||
end
|
||||
if params.type == 'kcp' then
|
||||
result.kcp_guise = params.headerType or "none"
|
||||
result.mtu = 1350
|
||||
result.tti = 50
|
||||
result.uplink_capacity = 5
|
||||
result.downlink_capacity = 20
|
||||
result.read_buffer_size = 2
|
||||
result.write_buffer_size = 2
|
||||
result.seed = params.seed or ""
|
||||
end
|
||||
if info.net == 'quic' then
|
||||
result.quic_guise = "none"
|
||||
result.quic_key = ""
|
||||
result.quic_security = "none"
|
||||
end
|
||||
if params.type == 'grpc' then
|
||||
result.serviceName = params.serviceName
|
||||
end
|
||||
if params.security == "tls" then
|
||||
result.security = "tls"
|
||||
result.tls_host = params.sni
|
||||
elseif params.security == "reality" then
|
||||
result.security = "reality"
|
||||
result.tls_host = params.sni
|
||||
result.publicKey = params.publicKey
|
||||
result.shortId = params.shortId
|
||||
result.fingerprint = params.fingerprint
|
||||
result.spiderX = params.spiderX
|
||||
else
|
||||
result.security = "none"
|
||||
end
|
||||
else
|
||||
result.server_port = host[2]
|
||||
end
|
||||
end
|
||||
if not result.alias then
|
||||
if result.server and result.server_port then
|
||||
result.alias = result.server .. ':' .. result.server_port
|
||||
else
|
||||
result.alias = "NULL"
|
||||
end
|
||||
end
|
||||
|
||||
local alias = result.alias
|
||||
result.alias = nil
|
||||
result.hashkey = md5(jsonStringify(result))
|
||||
result.alias = alias
|
||||
return result
|
||||
end
|
||||
-- wget
|
||||
local function wget(url)
|
||||
local stdout = luci.sys.exec('uclient-fetch -q --user-agent="Luci-app-xclient/OpenWRT" --no-check-certificate -O- "' .. url .. '"')
|
||||
return trim(stdout)
|
||||
end
|
||||
|
||||
local function check_filer(result)
|
||||
do
|
||||
|
||||
local filter_word = split(filter_words, "/")
|
||||
|
||||
local check_save = false
|
||||
if save_words ~= nil and save_words ~= "" and save_words ~= "NULL" then
|
||||
check_save = true
|
||||
end
|
||||
local save_word = split(save_words, "/")
|
||||
|
||||
|
||||
local filter_result = false
|
||||
local save_result = true
|
||||
|
||||
|
||||
for i, v in pairs(filter_word) do
|
||||
if tostring(result.alias):find(v) then
|
||||
filter_result = true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if check_save == true then
|
||||
for i, v in pairs(save_word) do
|
||||
if tostring(result.alias):find(v) then
|
||||
save_result = false
|
||||
end
|
||||
end
|
||||
else
|
||||
save_result = false
|
||||
end
|
||||
|
||||
|
||||
if filter_result == true or save_result == true then
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local execute = function()
|
||||
-- exec
|
||||
do
|
||||
if proxy == '0' then
|
||||
log('service is pausing..')
|
||||
luci.sys.init.stop(name)
|
||||
end
|
||||
if subscribe_url then
|
||||
local raw = wget(subscribe_url)
|
||||
if #raw > 0 then
|
||||
local nodes, szType
|
||||
local groupHash = md5(subscribe_url)
|
||||
cache[groupHash] = {}
|
||||
tinsert(nodeResult, {})
|
||||
local index = #nodeResult
|
||||
if jsonParse(raw) then
|
||||
nodes = jsonParse(raw).servers or jsonParse(raw)
|
||||
if nodes[1].server and nodes[1].method then
|
||||
szType = 'sip008'
|
||||
end
|
||||
else
|
||||
nodes = split(base64Decode(raw):gsub(" ", "_"), "\n")
|
||||
end
|
||||
for _, v in ipairs(nodes) do
|
||||
if v then
|
||||
local result
|
||||
if szType then
|
||||
result = processData(szType, v)
|
||||
elseif not szType then
|
||||
local node = trim(v)
|
||||
local dat = split(node, "://")
|
||||
if dat and dat[1] and dat[2] then
|
||||
local dat3 = ""
|
||||
if dat[3] then
|
||||
dat3 = "://" .. dat[3]
|
||||
end
|
||||
if dat[1] == 'ss' or dat[1] == 'trojan' then
|
||||
result = processData(dat[1], dat[2] .. dat3)
|
||||
else
|
||||
result = processData(dat[1], base64Decode(dat[2]))
|
||||
end
|
||||
end
|
||||
else
|
||||
log('skip unknown types: ' .. szType)
|
||||
end
|
||||
-- log(result)
|
||||
if result then
|
||||
if not result.server or not result.server_port or result.alias == "NULL" or check_filer(result) or result.server:match("[^0-9a-zA-Z%-%.%s]") or cache[groupHash][result.hashkey] then
|
||||
log('Discard invalid nodes: 【' .. result.type .. ' 】, ' .. result.alias)
|
||||
else
|
||||
result.grouphashkey = groupHash
|
||||
tinsert(nodeResult[index], result)
|
||||
cache[groupHash][result.hashkey] = nodeResult[index][#nodeResult[index]]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
log('Number of nodes successfully resolved: ' .. #nodes)
|
||||
else
|
||||
log(subscribe_url .. ': get content is empty')
|
||||
end
|
||||
end
|
||||
end
|
||||
-- diff
|
||||
do
|
||||
if next(nodeResult) == nil then
|
||||
log("Update failed, no node info available")
|
||||
return
|
||||
end
|
||||
local add, del = 0, 0
|
||||
ucic:foreach(name, uciType, function(old)
|
||||
if old.grouphashkey or old.hashkey then
|
||||
if not nodeResult[old.grouphashkey] or not nodeResult[old.grouphashkey][old.hashkey] then
|
||||
ucic:delete(name, old['.name'])
|
||||
del = del + 1
|
||||
else
|
||||
local dat = nodeResult[old.grouphashkey][old.hashkey]
|
||||
ucic:tset(name, old['.name'], dat)
|
||||
setmetatable(nodeResult[old.grouphashkey][old.hashkey], {__index = {_ignore = true}})
|
||||
end
|
||||
else
|
||||
if not old.alias then
|
||||
if old.server or old.server_port then
|
||||
old.alias = old.server .. ':' .. old.server_port
|
||||
log('Ignore manually added nodes: ' .. old.alias)
|
||||
else
|
||||
ucic:delete(name, old['.name'])
|
||||
end
|
||||
else
|
||||
log('Ignore manually added nodes: ' .. old.alias)
|
||||
end
|
||||
end
|
||||
end)
|
||||
for k, v in ipairs(nodeResult) do
|
||||
for kk, vv in ipairs(v) do
|
||||
if not vv._ignore then
|
||||
local section = ucic:add(name, uciType)
|
||||
ucic:tset(name, section, vv)
|
||||
add = add + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
ucic:commit(name)
|
||||
local globalServer = ucic:get_first(name, 'global', 'global_server', '')
|
||||
if globalServer ~= "nil" then
|
||||
local firstServer = ucic:get_first(name, uciType)
|
||||
if firstServer then
|
||||
if not ucic:get(name, globalServer) then
|
||||
luci.sys.call("/etc/init.d/" .. name .. " stop > /dev/null 2>&1 &")
|
||||
ucic:commit(name)
|
||||
ucic:set(name, ucic:get_first(name, 'global'), 'global_server', ucic:get_first(name, uciType))
|
||||
ucic:commit(name)
|
||||
log('The current active server has been deleted and is being automatically replaced with the first node。')
|
||||
luci.sys.call("/etc/init.d/" .. name .. " start > /dev/null 2>&1 &")
|
||||
else
|
||||
log('Maintain the current servers。')
|
||||
luci.sys.call("/etc/init.d/" .. name .. " boot > /dev/null 2>&1 &")
|
||||
end
|
||||
else
|
||||
log('There is no server node, stop the service')
|
||||
luci.sys.call("/etc/init.d/" .. name .. " stop > /dev/null 2>&1 &")
|
||||
end
|
||||
end
|
||||
log('New Nodes: ' .. add, 'Deleted Nodes: ' .. del)
|
||||
log('Subscription updated successfully')
|
||||
end
|
||||
end
|
||||
|
||||
if subscribe_url and #subscribe_url > 0 then
|
||||
xpcall(execute, function(e)
|
||||
log(e)
|
||||
log(debug.traceback())
|
||||
log('An error occurred, restoring service')
|
||||
local firstServer = ucic:get_first(name, uciType)
|
||||
if firstServer then
|
||||
luci.sys.call("/etc/init.d/" .. name .. " boot > /dev/null 2>&1 &")
|
||||
log('Service Restart Successful')
|
||||
else
|
||||
luci.sys.call("/etc/init.d/" .. name .. " stop > /dev/null 2>&1 &")
|
||||
log('Service Stop Successful')
|
||||
end
|
||||
end)
|
||||
end
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
Reference in new issue
Block a user