🌴 Sync 2026-03-05 23:51:42

This commit is contained in:
github-actions[bot] committed 2026-03-05 23:51:42 +08:00
1 parent 97c049ed0a
commit 0acff03804
14253 files changed
+3893239

No files matched your search

+124
View File
@@ -0,0 +1,124 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-xclient
PKG_VERSION:=v2.1
PKG_RELEASE:=1
PKG_MAINTAINER:=xcode75
include $(INCLUDE_DIR)/package.mk
define Package/$(PKG_NAME)
SECTION:=luci
CATEGORY:=LuCI
SUBMENU:=2. XClient
TITLE:=LuCI Support for Xray
PKGARCH:=all
MAINTAINER:=xcode75
DEPENDS:=+coreutils +coreutils-base64 +pdnsd-alt +coreutils-nohup +curl +dnsmasq-full +luci-compat +dns2socks +ipset +ip-full +iptables-mod-tproxy +libuci-lua +lua +luci-lib-jsonc +microsocks +tcping +resolveip +shadowsocks-libev-ss-local +shadowsocks-libev-ss-redir +unzip +uclient-fetch +wget-ssl +libmbedtls
endef
define Package/$(PKG_NAME)/config
endef
define Build/Configure
endef
define Build/Compile
endef
define Package/$(PKG_NAME)/conffiles
/etc/config/xclient
endef
define Package/$(PKG_NAME)/prerm
#!/bin/sh
if [ -z "$${IPKG_INSTROOT}" ]; then
echo "Removing rc.d symlink for xclient"
/etc/init.d/xclient disable
/etc/init.d/xclient stop
echo "Removing firewall rule for xclient"
uci -q batch <<-EOF >/dev/null
delete firewall.xclient
commit firewall
chmod -R 777 /usr/share/xclient 2>/dev/null
EOF
fi
exit 0
endef
define Package/$(PKG_NAME)/preinst
#!/bin/sh
if [ -z "$${IPKG_INSTROOT}" ]; then
rm -rf /tmp/dnsmasq.xclient 2>/dev/null
mv /etc/config/xclient /etc/config/xclient.bak 2>/dev/null
rm -rf /usr/lib/lua/luci/model/cbi/xclient 2>/dev/null
rm -rf /usr/lib/lua/luci/view/xclient 2>/dev/null
chmod -R 777 /usr/share/xclient 2>/dev/null
fi
exit 0
endef
define Package/$(PKG_NAME)/postinst
#!/bin/sh
if [ -z "$${IPKG_INSTROOT}" ]; then
rm -rf /tmp/luci*
mv /etc/config/xclient.bak /etc/config/xclient 2>/dev/null
/etc/init.d/xclient disable 2>/dev/null
chmod +x /etc/init.d/xclient 2>/dev/null
/etc/init.d/xclient disable 2>/dev/null
/etc/init.d/xclient stop 2>/dev/null
chmod -R 777 /usr/share/xclient 2>/dev/null
fi
exit 0
endef
define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/usr/lib/lua/luci
$(INSTALL_DIR) $(1)/usr/lib/lua/luci/controller
$(INSTALL_DIR) $(1)/usr/lib/lua/luci/model/cbi/xclient
$(INSTALL_DIR) $(1)/usr/lib/lua/luci/view/xclient
$(INSTALL_DATA) ./luasrc/controller/* $(1)/usr/lib/lua/luci/controller
$(INSTALL_DATA) ./luasrc/model/cbi/xclient/* $(1)/usr/lib/lua/luci/model/cbi/xclient
$(INSTALL_DATA) ./luasrc/view/xclient/* $(1)/usr/lib/lua/luci/view/xclient
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_DIR) $(1)/etc/uci-defaults
$(INSTALL_BIN) ./root/etc/init.d/* $(1)/etc/init.d
$(INSTALL_CONF) ./root/etc/config/* $(1)/etc/config
$(INSTALL_CONF) ./root/etc/uci-defaults/* $(1)/etc/uci-defaults
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_DIR) $(1)/usr/share/xclient
$(INSTALL_DATA) ./root/usr/bin/* $(1)/usr/bin
$(INSTALL_DATA) ./root/usr/share/xclient/* $(1)/usr/share/xclient
$(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
$(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/luci-app-xclient.json $(1)/usr/share/rpcd/acl.d/luci-app-xclient.json
$(INSTALL_DIR) $(1)/usr/share/luci/menu.d
$(INSTALL_DATA) ./root/usr/share/luci/menu.d/luci-app-xclient.json $(1)/usr/share/luci/menu.d/luci-app-xclient.json
$(INSTALL_DIR) $(1)/www
$(INSTALL_DIR) $(1)/www/luci-static
$(INSTALL_DIR) $(1)/www/luci-static/xclient
$(INSTALL_DIR) $(1)/www/luci-static/xclient/img
cp -pR ./root/www/luci-static/xclient/img/*.png $(1)/www/luci-static/xclient/img
cp -pR ./root/www/luci-static/xclient/img/*.svg $(1)/www/luci-static/xclient/img
endef
$(eval $(call BuildPackage,$(PKG_NAME)))
@@ -0,0 +1,525 @@
module("luci.controller.xclient", package.seeall)
require "nixio.fs"
require "luci.http"
require "luci.sys"
require "luci.model.uci"
local json = require "luci.jsonc"
function index()
if not nixio.fs.access("/etc/config/xclient") then
return
end
local page = entry({"admin", "services", "xclient"},alias("admin", "services", "xclient", "overview"), "XClient", 1)
page.dependent = true
page.acl_depends = {"luci-app-xclient"}
entry({"admin", "services", "xclient", "overview"},cbi("xclient/status"), "Overview", 5).leaf = true
entry({"admin", "services", "xclient", "servers" },cbi("xclient/servers"), "Servers", 10).leaf = true
entry({"admin", "services", "xclient", "server"},cbi("xclient/server"), nil).leaf = true
entry({"admin", "services", "xclient", "dns"},cbi("xclient/dns"),"DNS", 20).leaf = true
entry({"admin", "services", "xclient", "add-dns"},cbi("xclient/add-dns"), nil).leaf = true
entry({"admin", "services", "xclient", "routing"},cbi("xclient/rules"),"Routing", 30).leaf = true
entry({"admin", "services", "xclient", "rule"},cbi("xclient/rule"), nil).leaf = true
entry({"admin", "services", "xclient", "update"},cbi("xclient/update"),"Update", 40).leaf = true
entry({"admin", "services", "xclient", "log"},form("xclient/log"),"Log", 50).leaf = true
entry({"admin", "services", "xclient", "ping"}, call("act_ping")).leaf=true
entry({"admin", "services", "xclient", "run"},call("action_run")).leaf=true
entry({"admin", "services", "xclient", "subscribe"}, call("subscribe"))
entry({"admin", "services", "xclient", "update_geosite"}, call("update_geosite")).leaf = true
entry({"admin", "services", "xclient", "update_geoip"}, call("update_geoip")).leaf = true
entry({"admin", "services", "xclient", "logstatus"},call("logstatus_check")).leaf=true
entry({"admin", "services", "xclient", "readlog"},call("action_read")).leaf=true
entry({'admin', 'services', "xclient", 'web'}, call('web_check')).leaf=true
entry({"admin", "services", "xclient", "delete"}, call("act_delete"))
entry({"admin", "services", "xclient", "info"}, call("act_info"))
entry({"admin", "services", "xclient", "logout"}, call("act_logout"))
entry({"admin", "services", "xclient", "login"}, call("act_login"))
entry({"admin", "services", "xclient", "login_check"}, call("act_login_check"))
entry({"admin", "services", "xclient", "login_info"}, call("act_login_info"))
--entry({"admin", "services", "xclient", "xray_check"}, call("xray_check")).leaf = true
--entry({"admin", "services", "xclient", "xray_update"}, call("xrayx_update")).leaf = true
entry({"admin", "services", "xclient", "geolocation"}, call("geoloc")).leaf = true
entry({"admin", "services", "xclient", "geoip"}, call("geoip_check")).leaf=true
entry({"admin", "services", "xclient", "geoipupdate"}, call("geoip_update")).leaf=true
entry({"admin", "services", "xclient", "check_geoip"}, call("check_geoip_log")).leaf=true
entry({"admin", "services", "xclient", "geosite"}, call("geosite_check")).leaf=true
entry({"admin", "services", "xclient", "geositeupdate"}, call("geosite_update")).leaf=true
entry({"admin", "services", "xclient", "check_geosite"}, call("check_geosite_log")).leaf=true
entry({"admin", "services", "xclient", "version"}, call("check_version")).leaf=true
entry({"admin", "services", "xclient", "check_latest"}, call("check_latest")).leaf=true
entry({"admin", "services", "xclient", "traffic"}, call("statistics")).leaf=true
end
local uci = luci.model.uci.cursor()
local Process_list = luci.sys.exec("busybox ps -w")
local function dns_status()
if Process_list:find("xclient/bin/pdnsd") or (Process_list:find("xclient.dns") and Process_list:find("dns2socks.127.0.0.1.*127.0.0.1.5335")) then
return 1
else
return 0
end
end
local function tcp_status()
if Process_list:find("local.udp.xclient.retcp") or Process_list:find("tcp.udp.xclient.retcp") or Process_list:find("local.xclient.retcp") or Process_list:find("tcp.only.xclient.retcp") then
return 1
else
return 0
end
end
local function udp_status()
if Process_list:find("local.udp.xclient.retcp") or Process_list:find("tcp.udp.xclient.retcp") or Process_list:find("local.xclient.retcp") or Process_list:find("udp.only.xclient.reudp") then
return 1
else
return 0
end
end
local function socks_status()
if Process_list:find("local.udp.xclient.retcp") or Process_list:find("tcp.udp.xclient.local") or Process_list:find("local.xclient.retcp") then
return 1
else
return 0
end
end
local function running_status()
return luci.sys.call("busybox ps -w | grep xclient-retcp | grep -v grep >/dev/null") == 0
end
local function http_write_json(content)
luci.http.prepare_content("application/json")
luci.http.write_json(content or {code = 1})
end
function subscribe()
luci.http.prepare_content("application/json")
local token, details, data, suburl
token = uci:get("xclient", "config", "token")
local site = uci:get("xclient", "config", "site")
details = luci.sys.exec(string.format("curl -sL -H 'Content-Type: application/json' -d '{\"token\":\"%s\"}' -X POST %s/api/v1/details", token, site))
if details then
data = json.parse(details)
if data and data.ret == 1 then
suburl = data.url.xclient
if suburl then
uci:set("xclient", "config", "subscribe_url", suburl)
uci:commit("xclient")
luci.sys.call("/usr/bin/lua /usr/share/xclient/subscribe.lua >>/var/log/xclient.log")
luci.http.write_json({ret = 1})
end
else
luci.http.write_json({ret = 0})
end
else
luci.http.write_json({ret = 0})
end
end
function act_login_check()
luci.http.prepare_content("application/json")
local token = uci:get("xclient", "config", "token")
if token then
luci.http.write_json({login = 1})
else
luci.http.write_json({login = 0})
end
end
function act_login()
luci.http.prepare_content("application/json")
local info, token
local email = uci:get("xclient", "config", "email")
local passwd = uci:get("xclient", "config", "passwd")
local site = uci:get("xclient", "config", "site")
if email and passwd then
info = luci.sys.exec(string.format("curl -sL -H 'Content-Type: application/json' -d '{\"email\":\"%s\", \"passwd\":\"%s\"}' -X POST %s/api/v1/login", email, passwd, site))
if info then
info = json.parse(info)
else
luci.http.write_json({login = 0})
end
if info and info.ret == 1 then
token = info.token
uci:set("xclient", "config", "token", token)
uci:set("xclient", "config", "auto_update_servers", "6")
uci:commit("xclient")
luci.http.write_json({login = 1})
else
luci.http.write_json({login = 0})
end
else
luci.http.write_json({login = 0})
end
end
function login_info()
uci:set("xclient", "config", "email", luci.http.formvalue("email"))
uci:set("xclient", "config", "passwd", luci.http.formvalue("passwd"))
uci:set("xclient", "config", "site", luci.http.formvalue("weburl"))
uci:commit("xclient")
return 1
end
function act_logout()
luci.http.prepare_content("application/json")
local token, details, data
token = uci:get("xclient", "config", "token")
local site = uci:get("xclient", "config", "site")
details = luci.sys.exec(string.format("curl -sL -H 'Content-Type: application/json' -d '{\"token\":\"%s\"}' -X POST %s/api/v1/logout", token, site))
if details then
data = json.parse(details)
if data.ret == 1 then
uci:delete("xclient", "config", "token")
uci:delete("xclient", "config", "subscribe_url")
uci:delete("xclient", "config", "auto_update_servers")
uci:delete_all("xclient", "servers", function(s)
if s.hashkey or s.isSubscribe then
return true
else
return false
end
end)
uci:commit("xclient")
luci.sys.call("/etc/init.d/xclient boot &")
luci.http.write_json({logout = 1;})
else
luci.http.write_json({logout = 0;})
end
else
luci.http.write_json({logout = 0})
end
end
function act_login_info()
luci.http.prepare_content("application/json")
luci.http.write_json({
login_info = login_info();
})
end
function action_run()
luci.http.prepare_content("application/json")
luci.http.write_json({
dns_status = dns_status(),
tcp_status = tcp_status(),
udp_status = udp_status(),
socks_status = socks_status(),
})
end
function act_ping()
local e = {}
local domain = luci.http.formvalue("domain")
local port = luci.http.formvalue("port")
local transport = luci.http.formvalue("transport")
local wsPath = luci.http.formvalue("wsPath")
local tls = luci.http.formvalue("tls")
e.index = luci.http.formvalue("index")
local iret = luci.sys.call("ipset add xclient_spec_wan_ac " .. domain .. " 2>/dev/null")
if transport == "ws" then
local prefix = tls=='1' and "https://" or "http://"
local address = prefix..domain..':'..port..wsPath
local result = luci.sys.exec("curl --http1.1 -m 2 -ksN -o /dev/null -w 'time_connect=%{time_connect}\nhttp_code=%{http_code}' -H 'Connection: Upgrade' -H 'Upgrade: websocket' -H 'Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==' -H 'Sec-WebSocket-Version: 13' "..address)
e.socket = string.match(result,"http_code=(%d+)")=="101"
e.ping = tonumber(string.match(result, "time_connect=(%d+.%d%d%d)"))*1000
else
local socket = nixio.socket("inet", "stream")
socket:setopt("socket", "rcvtimeo", 3)
socket:setopt("socket", "sndtimeo", 3)
e.socket = socket:connect(domain, port)
socket:close()
-- e.ping = luci.sys.exec("ping -c 1 -W 1 %q 2>&1 | grep -o 'time=[0-9]*.[0-9]' | awk -F '=' '{print$2}'" % domain)
-- if (e.ping == "") then
e.ping = luci.sys.exec(string.format("echo -n $(tcping -q -c 1 -i 1 -t 2 -p %s %s 2>&1 | grep -o 'time=[0-9]*' | awk -F '=' '{print $2}') 2>/dev/null", port, domain))
end
if (iret == 0) then
luci.sys.call(" ipset del xclient_spec_wan_ac " .. domain)
end
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function logstatus_check()
luci.http.prepare_content("text/plain; charset=utf-8")
local fdp=tonumber(nixio.fs.readfile("/usr/share/xclient/logstatus_check")) or 0
local f=io.open("/usr/share/xclient/xclient.txt", "r+")
f:seek("set",fdp)
local a=f:read(2048000) or ""
fdp=f:seek()
nixio.fs.writefile("/usr/share/xclient/logstatus_check",tostring(fdp))
f:close()
if nixio.fs.access("/usr/share/xclient/logstatus_check") then
luci.http.write(a)
else
luci.http.write(a.."\0")
end
end
local function readlog()
return luci.sys.exec("sed -n '$p' /usr/share/xclient/readlog.txt 2>/dev/null")
end
function action_read()
luci.http.prepare_content("application/json")
luci.http.write_json({
readlog = readlog();
})
end
function check(host, port)
local socket = nixio.socket("inet", "stream")
socket:setopt("socket", "rcvtimeo", 3)
socket:setopt("socket", "sndtimeo", 3)
local ret = socket:connect(host, port)
socket:close()
return ret
end
function web_check()
local e = {}
local port = 80
e.baidu = check('www.baidu.com', port)
e.taobao = check('www.taobao.com', port)
e.google = check('www.google.com', port)
e.youtube = check('www.youtube.com', port)
luci.http.prepare_content('application/json')
luci.http.write_json(e)
end
function update_geoip()
luci.sys.call("lua /usr/share/xclient/geoip.lua log > /dev/null 2>&1 &")
http_write_json()
end
function update_geosite()
luci.sys.call("lua /usr/share/xclient/geosite.lua log > /dev/null 2>&1 &")
http_write_json()
end
function act_delete()
luci.sys.call("/etc/init.d/xclient reload &")
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "xclient", "servers"))
end
function geoloc()
local geo, data
geo = luci.sys.exec(string.format("curl -sL -H 'User-Agent: luci-app-xclient' 'Content-Type: application/json' -X GET https://query-geolocation.herokuapp.com/?lang=en"))
if geo then
data = json.parse(geo)
end
luci.http.prepare_content("application/json")
luci.http.write_json({
code = data.code,
location = data.location
})
end
local function geoipcheck()
if nixio.fs.access("/var/run/geo_ip_update_error") then
return "0"
elseif nixio.fs.access("/var/run/geo_ip_update") then
return "1"
elseif nixio.fs.access("/var/run/geo_ip_down_complete") then
return "2"
end
end
local function geositecheck()
if nixio.fs.access("/var/run/geo_site_update_error") then
return "0"
elseif nixio.fs.access("/var/run/geo_site_update") then
return "1"
elseif nixio.fs.access("/var/run/geo_site_down_complete") then
return "2"
end
end
function geoip_check()
luci.http.prepare_content("application/json")
luci.http.write_json({
geoipcheck = geoipcheck();
})
end
function geosite_check()
luci.http.prepare_content("application/json")
luci.http.write_json({
geositecheck = geositecheck();
})
end
function geoip_update()
nixio.fs.writefile("/var/run/geoiplog","0")
luci.sys.exec("(rm /var/run/geo_ip_update_error ; touch /var/run/geo_ip_update ; sh /usr/share/xclient/geoip.sh >/tmp/geo_ip_update.txt 2>&1 || touch /var/run/geo_ip_update_error ;rm /var/run/geo_ip_update) &")
end
function geosite_update()
nixio.fs.writefile("/var/run/geositelog","0")
luci.sys.exec("(rm /var/run/geo_site_update_error ; touch /var/run/geo_site_update ; sh /usr/share/xclient/geosite.sh >/tmp/geo_site_update.txt 2>&1 || touch /var/run/geo_site_update_error ;rm /var/run/geo_site_update) &")
end
function check_geoip_log()
luci.http.prepare_content("text/plain; charset=utf-8")
local fdp=tonumber(nixio.fs.readfile("/var/run/geoiplog")) or 0
local f=io.open("/tmp/geo_ip_update.txt", "r+")
f:seek("set",fdp)
local a=f:read(2048000) or ""
fdp=f:seek()
nixio.fs.writefile("/var/run/geoiplog",tostring(fdp))
f:close()
if nixio.fs.access("/var/run/geo_ip_update") then
luci.http.write(a)
else
luci.http.write(a.."\0")
end
end
function check_geosite_log()
luci.http.prepare_content("text/plain; charset=utf-8")
local fdp=tonumber(nixio.fs.readfile("/var/run/geositelog")) or 0
local f=io.open("/tmp/geo_site_update.txt", "r+")
f:seek("set",fdp)
local a=f:read(2048000) or ""
fdp=f:seek()
nixio.fs.writefile("/var/run/geositelog",tostring(fdp))
f:close()
if nixio.fs.access("/var/run/geo_site_update") then
luci.http.write(a)
else
luci.http.write(a.."\0")
end
end
local function core_version()
if nixio.fs.access("/usr/bin/xray") then
if nixio.fs.access("/usr/share/xclient/core_version") then
return luci.sys.exec("sed -n 1p /usr/share/xclient/core_version")
else
return luci.sys.exec("/usr/bin/xray -version | awk '{print $2}' | sed -n 1P")
end
else
return "--"
end
end
local function latest_core()
if nixio.fs.access("/usr/share/xclient/new_core") then
return luci.sys.exec("sed -n 1p /usr/share/xclient/new_core")
else
return "--"
end
end
local function geosite_version()
if nixio.fs.access("/usr/bin/geosite.dat") then
if nixio.fs.access("/usr/share/xclient/geosite_version") then
return luci.sys.exec("sed -n 1p /usr/share/xclient/geosite_version")
else
return "--"
end
else
return "--"
end
end
local function geoip_version()
if nixio.fs.access("/usr/bin/geoip.dat") then
if nixio.fs.access("/usr/share/xclient/geoip_version") then
return luci.sys.exec("sed -n 1p /usr/share/xclient/geoip_version")
else
return "--"
end
else
return "--"
end
end
function check_latest()
return luci.sys.exec("sh /usr/share/xclient/core_version.sh")
end
function check_version()
luci.http.prepare_content("application/json")
luci.http.write_json({
core_version = core_version(),
geoip_version = geoip_version(),
geosite_version = geosite_version(),
latest_core = latest_core()
})
end
function statistics()
luci.http.prepare_content("application/json")
local up, check_uplink
check_uplink = luci.sys.exec(string.format("xray api stats --server=127.0.0.1:8888 -name \"outbound>>>proxy_outbound>>>traffic>>>uplink\""))
if check_uplink then
up = json.parse(check_uplink)
if up ~= nil then
if up.stat.value ~= nil then
local uplink = up.stat.value
else
local uplink = 0
end
else
local uplink = 0
end
else
local uplink = 0
end
local down, check_downlink
check_downlink = luci.sys.exec(string.format("xray api stats --server=127.0.0.1:8888 -name \"outbound>>>proxy_outbound>>>traffic>>>downlink\""))
if check_downlink then
down = json.parse(check_downlink)
if down ~= nil then
if down.stat.value ~= nil then
local downlink = down.stat.value
else
local downlink = 0
end
else
local downlink = 0
end
else
local downlink = 0
end
luci.http.write_json({
downlink = downlink,
uplink = uplink
})
end
@@ -0,0 +1,42 @@
local m, s, o
local xclient = "xclient"
local uci = luci.model.uci.cursor()
local fs = require "nixio.fs"
local sys = require "luci.sys"
local sid = arg[1]
m = Map(xclient, "Add/Edit DNS Rule")
--m.pageaction = false
m.redirect = luci.dispatcher.build_url("admin/services/xclient/dns")
if m.uci:get(xclient, sid) ~= "dns" then
luci.http.redirect(m.redirect)
return
end
s = m:section(NamedSection, sid, "dns")
s.anonymous = true
s.addremove = false
o = s:option(Value, "address", "Address")
o.rmempty = true
o = s:option(Value, "port", "Port")
o.rmempty = false
o = s:option(DynamicList, "domains", "Domains")
o.rmempty = true
o = s:option(DynamicList, "expectIPs", "expectIPs")
o.rmempty = true
o = s:option(Flag, "skipFallback", "skipFallback")
o.rmempty = true
o.description = "Refer To Documentation"
..translatef("<a href=\"%s\" target=\"_blank\">" .. "%s".."</a>", "https://xtls.github.io/Xray-docs-next/config/dns.html", " Here")
return m
@@ -0,0 +1,115 @@
local m, s, o
local xclient = "xclient"
local uci = luci.model.uci.cursor()
m = Map(xclient)
s = m:section(TypedSection, "global", "DNS Servers")
s.anonymous = true
s.addremove = false
o = s:option(DynamicList, "dns_servers", "Servers")
o.rmempty = false
o = s:option(Value, 'clientIp', 'Client Ip')
o.rmempty = true
o = s:option(ListValue, 'queryStrategy', 'Query Strategy')
o:value("UseIP", "UseIP")
o:value("UseIPv4", "UseIPv4")
o:value("UseIPv6", "UseIPv6")
o.rmempty = true
o = s:option(ListValue, 'disableCache', 'Disable Cache')
o:value("0", "False")
o:value("1", "True")
o.rmempty = true
o = s:option(ListValue, 'disableFallback', 'Disable Fallback')
o:value("0", "False")
o:value("1", "True")
o.rmempty = true
o = s:option(ListValue, 'disableFallbackIfMatch', 'Disable Fallback If Match')
o:value("0", "False")
o:value("1", "True")
o.rmempty = true
o.description = "Refer To Documentation"
..translatef("<a href=\"%s\" target=\"_blank\">" .. "%s".."</a>", "https://xtls.github.io/Xray-docs-next/config/dns.html", " Here")
y = Map("xclient")
y.pageaction = true
x = y:section(TypedSection, "dns", "DNS Rules")
x.anonymous = true
x.addremove = true
x.sortable = true
x.template = "cbi/tblsection"
x.extedit = luci.dispatcher.build_url("admin/services/xclient/add-dns/%s")
function x.create(...)
local sid = TypedSection.create(...)
if sid then
luci.http.redirect(x.extedit % sid)
return
end
end
o = x:option(DummyValue, "address", "Address")
function o.cfgvalue(...)
return Value.cfgvalue(...) or "—"
end
o = x:option(DummyValue, "port", "Port")
function o.cfgvalue(...)
return Value.cfgvalue(...) or "—"
end
o = x:option(DummyValue, "domains", "Domains")
o.rawhtml=true
function o.cfgvalue(t,e)
local t,e=t.map:get(e,"domains"),""
if t then
for a,t in pairs(t)do
e=e..t.."<br>"
end
return e
else
return"&#8212;"
end
end
o = x:option(DummyValue, "expectIPs", "ExpectIPs")
o.rawhtml=true
function o.cfgvalue(t,e)
local t,e=t.map:get(e,"expectIPs"),""
if t then
for a,t in pairs(t)do
e=e..t.."<br>"
end
return e
else
return"&#8212;"
end
end
o = x:option(Flag, "skipFallback", "SkipFallback")
o.rmempty = false
o.default = o.skipFallback
o.cfgvalue = function(...)
return Flag.cfgvalue(...) or "0"
end
local apply = luci.http.formvalue("cbi.apply")
if apply then
m.uci:commit("xclient")
y.uci:commit("xclient")
luci.sys.call("/etc/init.d/xclient boot >/dev/null 2>&1 &")
end
return m,y
@@ -0,0 +1,14 @@
local m
m = Map("xclient")
s = m:section(TypedSection, "log")
m.pageaction = false
s.anonymous = true
s.addremove=false
l = s:option(TextValue, "log")
l.template = "xclient/logs"
return m
@@ -0,0 +1,67 @@
local m, s, o
local xclient = "xclient"
local uci = luci.model.uci.cursor()
local fs = require "nixio.fs"
local sys = require "luci.sys"
local sid = arg[1]
m = Map(xclient, "Add/Edit Rule")
--m.pageaction = false
m.redirect = luci.dispatcher.build_url("admin/services/xclient/routing")
if m.uci:get(xclient, sid) ~= "rule" then
luci.http.redirect(m.redirect)
return
end
s = m:section(NamedSection, sid, "rule")
s.anonymous = true
s.addremove = false
o = s:option(ListValue, "domainMatcher", "Domain Matcher")
o:value("linear", "linear")
o:value("mph", "mph")
o.default = "linear"
o.rmempty = true
o = s:option(DynamicList, "domain", "Domain")
o.rmempty = true
o = s:option(DynamicList, "ip", "IP")
o.rmempty = true
o = s:option(Value, "port", "Port")
o.rmempty = true
o = s:option(Value, "sourcePort", "SourcePort")
o.rmempty = true
o = s:option(Value, "network", "Network")
o.rmempty = true
o = s:option(DynamicList, "source", "Source")
o.rmempty = true
o = s:option(DynamicList, "inboundTag", "InboundTag")
o:value("proxy_inbound", "PROXY_INBOUND")
o:value("dns_inbound", "DNS_INBOUND")
o:value("socks_inbound", "SOCKS_INBOUND")
o.rmempty = false
o = s:option(DynamicList, "protocol", "Protocol")
o:value("http", "HTTP")
o:value("tls", "TLS")
o:value("bittorrent", "BITTORRENT")
o.rmempty = true
o = s:option(ListValue, "outboundTag", "OutboundTag")
o:value("proxy_outbound", "PROXY_OUTBOUND")
o:value("dns_outbound", "DNS_OUTBOUND")
o:value("direct", "DIRECT")
o:value("block", "BLOCK")
o.rmempty = false
o.description = "Refer To Documentation"
..translatef("<a href=\"%s\" target=\"_blank\">" .. "%s".."</a>", "https://xtls.github.io/Xray-docs-next/config/routing.html", " Here")
return m
@@ -0,0 +1,150 @@
require "luci.http"
local uci = luci.model.uci.cursor()
local q, y
z = Map("xclient")
s = z:section(TypedSection, "global", "Routing Settings")
s.anonymous = true
s.addremove = false
o = s:option(ListValue, 'routing_strategy', 'Query Strategy')
o:value("AsIs", "AsIs")
o:value("IPIfNonMatch", "IPIfNonMatch")
o:value("IPOnDemand", "IPOnDemand")
o.default = "IPIfNonMatch"
o.rmempty = true
o = s:option(ListValue, "domainMatcher", "Domain Matcher")
o:value("linear", "linear")
o:value("mph", "mph")
o.default = "linear"
o.rmempty = true
o.description = "Refer To Documentation"
..translatef("<a href=\"%s\" target=\"_blank\">" .. "%s".."</a>", "https://xtls.github.io/Xray-docs-next/config/routing.html", " Here")
y = Map("xclient")
y.pageaction = true
x = y:section(TypedSection, "rule", "Routing Rules")
x.anonymous = true
x.addremove = true
x.sortable = true
x.template = "cbi/tblsection"
x.extedit = luci.dispatcher.build_url("admin/services/xclient/rule/%s")
function x.create(...)
local sid = TypedSection.create(...)
if sid then
luci.http.redirect(x.extedit % sid)
return
end
end
o = x:option(DummyValue, "domainMatcher", "Domain Matcher")
function o.cfgvalue(...)
return Value.cfgvalue(...) or "—"
end
o = x:option(DummyValue, "domain", "Domain")
o.rawhtml=true
function o.cfgvalue(t,e)
local t,e=t.map:get(e,"domain"),""
if t then
for a,t in pairs(t)do
e=e..t.."<br>"
end
return e
else
return"&#8212;"
end
end
o = x:option(DummyValue, "ip", "IP")
o.rawhtml=true
function o.cfgvalue(t,e)
local t,e=t.map:get(e,"ip"),""
if t then
for a,t in pairs(t)do
e=e..t.."<br>"
end
return e
else
return"&#8212;"
end
end
o = x:option(DummyValue, "port", "Port")
function o.cfgvalue(...)
return Value.cfgvalue(...) or "—"
end
o = x:option(DummyValue, "sourcePort", "SourcePort")
function o.cfgvalue(...)
return Value.cfgvalue(...) or "—"
end
o = x:option(DummyValue, "network", "Network")
function o.cfgvalue(...)
return Value.cfgvalue(...) or "—"
end
o = x:option(DummyValue, "source", "Source")
o.rawhtml=true
function o.cfgvalue(t,e)
local t,e=t.map:get(e,"source"),""
if t then
for a,t in pairs(t)do
e=e..t.."<br>"
end
return e
else
return"&#8212;"
end
end
o = x:option(DummyValue, "inboundTag", "Inboundtag")
o.rawhtml=true
function o.cfgvalue(t,e)
local t,e=t.map:get(e,"inboundTag"),""
if t then
for a,t in pairs(t)do
e=e..t.."<br>"
end
return e
else
return"&#8212;"
end
end
o = x:option(DummyValue, "protocol", "Protocol")
o.rawhtml=true
function o.cfgvalue(t,e)
local t,e=t.map:get(e,"protocol"),""
if t then
for a,t in pairs(t)do
e=e..t.."<br>"
end
return e
else
return"&#8212;"
end
end
o = x:option(DummyValue, "outboundTag", "Outboundtag")
function o.cfgvalue(...)
return Value.cfgvalue(...) or "—"
end
local apply = luci.http.formvalue("cbi.apply")
if apply then
y.uci:commit("xclient")
z.uci:commit("xclient")
luci.sys.call("/etc/init.d/xclient boot >/dev/null 2>&1 &")
end
return z, y
@@ -0,0 +1,378 @@
require "nixio.fs"
require "luci.sys"
require "luci.http"
local m, s, o
local sid = arg[1]
local uuid = luci.sys.exec("cat /proc/sys/kernel/random/uuid")
function is_finded(e)
return luci.sys.exec('type -t -p "%s"' % e) ~= "" and true or false
end
local server_table = {}
local encrypt_methods_v2ray_ss = {
-- xray_ss
"none",
"plain",
-- aead
"aes-128-gcm",
"aes-256-gcm",
"chacha20-poly1305",
"chacha20-ietf-poly1305",
"xchacha20-ietf-poly1305",
"2022-blake3-aes-128-gcm",
"2022-blake3-aes-256-gcm"
}
local securitys = {
-- vmess
"auto",
"aes-128-gcm",
"chacha20-poly1305"
}
local flows = {
-- xlts
"xtls-rprx-vision",
"xtls-rprx-vision-udp443"
}
m = Map("xclient", "Add/Edit Server")
m.redirect = luci.dispatcher.build_url("admin/services/xclient/servers")
if m.uci:get("xclient", sid) ~= "servers" then
luci.http.redirect(m.redirect)
return
end
-- [[ Servers Setting ]]--
s = m:section(NamedSection, sid, "servers")
s.anonymous = true
s.addremove = false
o = s:option(ListValue, "protocol", "Protocol")
o:value("vmess", "Vmess")
o:value("vless", "Vless")
o:value("trojan", "Trojan")
o:value("shadowsocks", "Shadowsocks")
o:value("shadowsocks-plugin", "Shadowsocks-Plugin")
o = s:option(Value, "alias", "Remarks")
o = s:option(Value, "server", "Server Address")
o.datatype = "host"
o.rmempty = false
o = s:option(Value, "server_port", "Server Port")
o.datatype = "port"
o.rmempty = false
o = s:option(Value, "password", "Password")
o.password = true
o.rmempty = true
o:depends({protocol = "shadowsocks"})
o:depends({protocol = "shadowsocks-plugin"})
o:depends({protocol = "trojan"})
o = s:option(ListValue, "encrypt_method_v2ray_ss", "Encrypt Method")
for _, v in ipairs(encrypt_methods_v2ray_ss) do
o:value(v)
end
o.rmempty = true
o:depends({protocol = "shadowsocks"})
o:depends({protocol = "shadowsocks-plugin"})
o = s:option(Flag, "ivCheck", "Bloom Filter")
o.rmempty = true
o:depends({protocol = "shadowsocks"})
o.default = "1"
-- Shadowsocks Plugin
o = s:option(ListValue, "plugin", "Plugin")
-- o:value("none", "None")
if is_finded("xray-plugin") then
o:value("xray-plugin", "xray-plugin")
end
o.rmempty = true
o:depends("protocol", "shadowsocks-plugin")
o = s:option(Value, "plugin_opts", "Plugin Opts")
o.rmempty = true
o:depends({protocol = "shadowsocks-plugin", plugin = "v2ray-plugin"})
o:depends({protocol = "shadowsocks-plugin", plugin = "xray-plugin"})
-- VmessId
o = s:option(Value, "vmess_id", "UUID")
o.rmempty = true
o.default = uuid
o:depends({protocol = "vmess"})
o:depends({protocol = "vless"})
-- VLESS Encryption
o = s:option(Value, "vless_encryption", "Vless Encryption")
o.rmempty = true
o.default = "none"
o:depends({protocol = "vless"})
o = s:option(ListValue, "vmess_encryption", "Encrypt Method")
for _, v in ipairs(securitys) do
o:value(v, v:upper())
end
o.rmempty = true
o:depends({protocol = "vmess"})
-- Transport
o = s:option(ListValue, "transport", "Transport")
o:value("tcp", "TCP")
o:value("kcp", "mKCP")
o:value("ws", "WebSocket")
o:value("h2", "HTTP/2")
o:value("grpc", "gRPC")
o:value("quic", "QUIC")
o.rmempty = true
-- [[ TCP ]]--
o = s:option(ListValue, "tcp_guise", "Camouflage Type")
o:value("none", "None")
o:value("http", "HTTP")
o.rmempty = true
o:depends({protocol = "vmess",transport = "tcp"})
o:depends({protocol = "trojan" ,transport = "tcp"})
o:depends({protocol = "vless",transport = "tcp"})
o = s:option(Value, "http_host", "HTTP Host")
o:depends("tcp_guise", "http")
o.rmempty = true
o = s:option(Value, "http_path", "HTTP Path")
o:depends("tcp_guise", "http")
o.rmempty = true
-- [[ WS ]]--
o = s:option(Value, "ws_host", "WebSocket Host")
o:depends({transport = "ws"})
o.datatype = "hostname"
o.rmempty = true
o = s:option(Value, "ws_path", "WebSocket Path")
o:depends("transport", "ws")
o.rmempty = true
-- [[ H2 ]]--
o = s:option(Value, "h2_host", "HTTP/2 Host")
o:depends("transport", "h2")
o.rmempty = true
o = s:option(Value, "h2_path", "HTTP/2 Path")
o:depends("transport", "h2")
o.rmempty = true
-- gRPC
o = s:option(Value, "serviceName", "ServiceName")
o:depends("transport", "grpc")
o.rmempty = true
o = s:option(Value, "initial_windows_size", "Initial Windows Size")
o.datatype = "uinteger"
o:depends("transport", "grpc")
o.default = 0
o.rmempty = true
o = s:option(Flag, "health_check", "Health Check")
o:depends("transport", "h2")
o:depends("transport", "grpc")
o.rmempty = true
o = s:option(Value, "read_idle_timeout", "Idle Timeout")
o.datatype = "uinteger"
o:depends({health_check = true, transport = "h2"})
o.default = 60
o.rmempty = true
o = s:option(Value, "idle_timeout", "Idle Timeout")
o.datatype = "uinteger"
o:depends({health_check = true, transport = "grpc"})
o.default = 60
o.rmempty = true
o = s:option(Value, "health_check_timeout", "Health Check Timeout")
o.datatype = "uinteger"
o:depends("health_check", 1)
o.default = 20
o.rmempty = true
o = s:option(Flag, "permit_without_stream", "Permit Without Stream")
o:depends({health_check = true, transport = "grpc"})
o.rmempty = true
-- [[ mKCP ]]--
o = s:option(ListValue, "kcp_guise", "Camouflage Type")
o:depends("transport", "kcp")
o:value("none", "None")
o:value("srtp", "VideoCall (SRTP)")
o:value("utp", "BitTorrent (uTP)")
o:value("wechat-video", "WechatVideo")
o:value("dtls", "DTLS 1.2")
o:value("wireguard", "WireGuard")
o.rmempty = true
o = s:option(Value, "mtu", "MTU")
o.datatype = "uinteger"
o:depends("transport", "kcp")
o.default = 1350
o.rmempty = true
o = s:option(Value, "tti", "TTI")
o.datatype = "uinteger"
o:depends("transport", "kcp")
o.default = 50
o.rmempty = true
o = s:option(Value, "uplink_capacity", "Uplink Capacity")
o.datatype = "uinteger"
o:depends("transport", "kcp")
o.default = 5
o.rmempty = true
o = s:option(Value, "downlink_capacity", "Downlink Capacity")
o.datatype = "uinteger"
o:depends("transport", "kcp")
o.default = 20
o.rmempty = true
o = s:option(Value, "read_buffer_size", "Read Buffer Size")
o.datatype = "uinteger"
o:depends("transport", "kcp")
o.default = 2
o.rmempty = true
o = s:option(Value, "write_buffer_size", "Write Buffer Size")
o.datatype = "uinteger"
o:depends("transport", "kcp")
o.default = 2
o.rmempty = true
o = s:option(Value, "seed", "Seed (optional)")
o:depends({protocol = "vless", transport = "kcp"})
o.rmempty = true
o = s:option(Flag, "congestion", "Congestion")
o:depends("transport", "kcp")
o.rmempty = true
-- [[ QUIC ]]--
o = s.option(ListValue,"quic_security", "[quic] Security")
o:depends("transport", "quic")
o.default = "none"
o:value("none", "none")
o:value("aes-128-gcm", "aes-128-gcm")
o:value("chacha20-poly1305", "chacha20-poly1305")
o.rmempty = true
o = s.option(Value, "quic_key", "[quic] Key")
o:depends("transport", "quic")
o.rmempty = true
o = s.option(ListValue, "quic_guise", "[quic] Fake Header Type")
o:depends("transport", "quic")
o.default = "none"
o:value("none", _("None"))
o:value("srtp", _("VideoCall (SRTP)"))
o:value("utp", _("BitTorrent (uTP)"))
o:value("wechat-video", _("WechatVideo"))
o:value("dtls", "DTLS 1.2")
o:value("wireguard", "WireGuard")
o.rmempty = true
-- [[ security ]]--
o = s:option(ListValue, "security", "Security")
o.default = "none"
o:value("none", "none")
o:value("tls", "tls")
o:value("reality", "reality")
o.rmempty = false
-- Flow
o = s:option(Value, "vless_flow", "Flow")
for _, v in ipairs(flows) do
o:value(v, v)
end
o.rmempty = true
o.default = "xtls-rprx-vision"
o:depends({protocol = "vless", transport = "tcp"})
o = s:option(Value, "tls_host", "TLS Host / serverName")
o.datatype = "hostname"
o:depends({security = 'tls', security = 'reality'})
o.rmempty = true
o = s:option(Flag, "rejectUnknownSni", "Reject Unknown Sni")
o:depends({security = 'tls'})
o.rmempty = true
-- [[ Alpn ]]--
o = s:option(DynamicList, "alpn", "Alpn")
o:value("h2", "h2")
o:value("http/1.1", "http/1.1")
o:depends({security = 'tls'})
o.rmempty = true
-- [[ uTLS ]]--
o = s:option(ListValue, "fingerprint", "Finger Print")
o:value("", "disable")
o:value("firefox", "firefox")
o:value("chrome", "chrome")
o:value("safari", "safari")
o:value("randomized", "randomized")
o:depends({security = 'tls', security = 'reality'})
o.default = "chrome"
o.rmempty = true
-- [[ ShortId ]]--
o = s:option(Value, "shortId", "ShortId")
o.default = ""
o:depends({security = 'reality'})
o.rmempty = true
-- [[ publicKey ]]--
o = s:option(Value, "publicKey", "publicKey")
o.default = ""
o:depends({security = 'reality'})
o.rmempty = true
-- [[ spiderX ]]--
o = s:option(Value, "spiderX", "spiderX")
o.default = ""
o:depends({security = 'reality'})
o.rmempty = true
-- [[ allowInsecure ]]--
o = s:option(Flag, "insecure", "AllowInsecure")
o.rmempty = true
o:depends({security = 'tls'})
-- [[ Mux ]]--
o = s:option(Flag, "mux", "Mux")
o.rmempty = true
o:depends({protocol = "shadowsocks"})
o = s:option(Value, "concurrency", "Concurrency")
o.datatype = "uinteger"
o.rmempty = true
o.default = "8"
o:depends("mux", "1")
return m
@@ -0,0 +1,148 @@
require "luci.http"
require "luci.dispatcher"
require "luci.model.uci"
local m, s, o,c
local uci = luci.model.uci.cursor()
local server_count = 0
uci:foreach("xclient", "servers", function(s)
server_count = server_count + 1
end)
font_blue = [[<b color="blue">]]
font_green = [[<b style=color:green>]]
font_red = [[<b style=color:red>]]
font_off = [[</b>]]
bold_on = [[<strong>]]
bold_off = [[</strong>]]
c = Map("xclient")
-- Server Subscribe
s = c:section(TypedSection, "xclient", "Account Settings")
s.anonymous = true
o = s:option(Value, "site")
o.title = "Website Url"
o.rmempty = false
o = s:option(Value, "email")
o.title = "Email"
o.rmempty = true
o = s:option(Value, "passwd")
o.title = "Password"
o.password = true
o.rmempty = true
if c.uci:get("xclient", "config", "token") then
o = s:option(Value, "auto_update_servers")
o.title = "Auto Update (h)"
o.default=6
o.rmempty = true
end
o = s:option(DummyValue, "login", "Status")
if not c.uci:get("xclient", "config", "token") then
o.template = "xclient/login"
else
o.template = "xclient/logout"
end
local t = {
{Delete_Servers}
}
b = c:section(Table, t)
o = b:option(Button,"Delete_Servers")
o.inputtitle = "Delete All Servers"
o.inputstyle = "delete"
o.write = function()
uci:delete_all("xclient", "servers", function(s)
if s.hashkey or s.isSubscribe then
return true
else
return false
end
end)
uci:save("xclient")
uci:commit("xclient")
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "xclient", "delete"))
return
end
m = Map("xclient")
s = m:section(TypedSection, "servers", "Server List")
s.anonymous = true
s.addremove = true
s.template = "cbi/tblsection"
s.sortable = false
s.extedit = luci.dispatcher.build_url("admin", "services", "xclient", "server", "%s")
function s.create(...)
local sid = TypedSection.create(...)
if sid then
luci.http.redirect(s.extedit % sid)
return
end
end
o = s:option(DummyValue, "type", "Type")
function o.cfgvalue(self, section)
return m:get(section, "protocol") or Value.cfgvalue(self, section) or "N/A"
end
o = s:option(DummyValue, "alias", "Alias")
function o.cfgvalue(...)
return Value.cfgvalue(...) or "N/A"
end
o = s:option(DummyValue, "server", "Server Address")
function o.cfgvalue(...)
return Value.cfgvalue(...) or "N/A"
end
o = s:option(DummyValue, "server_port", "Server Port")
function o.cfgvalue(...)
return Value.cfgvalue(...) or "N/A"
end
o = s:option(DummyValue, "transport", "Transport")
function o.cfgvalue(...)
return Value.cfgvalue(...) or "N/A"
end
o = s:option(DummyValue, "server" ,"Latency")
o.template="xclient/ping"
o.width="10%"
o = s:option(DummyValue, "server_port", "Socket Connected")
o.template = "xclient/socket"
o.width = "10%"
o.render = function(self, section, scope)
self.transport = s:cfgvalue(section).transport
if self.transport == 'ws' then
self.ws_path = s:cfgvalue(section).ws_path
self.tls = s:cfgvalue(section).tls
end
DummyValue.render(self, section, scope)
end
m:append(Template("xclient/server_list"))
local apply = luci.http.formvalue("cbi.apply")
if apply then
c.uci:commit("xclient")
m.uci:commit("xclient")
luci.sys.call("/etc/init.d/xclient boot >/dev/null 2>&1 &")
end
return c, m
@@ -0,0 +1,162 @@
require "luci.sys"
require "luci.http"
local uci = require"luci.model.uci".cursor()
local m, s, sec, o
local uci = luci.model.uci.cursor()
font_green = [[<b style=color:green>]]
font_red = [[<b style=color:red>]]
font_off = [[</b>]]
bold_on = [[<strong>]]
bold_off = [[</strong>]]
local server_table = {}
uci:foreach("xclient", "servers", function(s)
if s.alias then
server_table[s[".name"]] = "[%s]:%s" % {string.upper(s.protocol or s.type), s.alias}
elseif s.server and s.server_port then
server_table[s[".name"]] = "[%s]:%s:%s" % {string.upper(s.protocol or s.type), s.server, s.server_port}
end
end)
local key_table = {}
for key,_ in pairs(server_table) do
table.insert(key_table,key)
end
table.sort(key_table)
m = Map("xclient")
m.pageaction = true
m:append(Template("xclient/status"))
s = m:section(TypedSection, "global")
s.anonymous = true
s.addremove = false
s:tab("main", "Client")
s:tab("settings", "Settings")
s:tab("access", "Access")
s:tab("ports", "Ports")
o = s:taboption("main", Flag, "enable", "Enable Client")
o.rmempty = true
o = s:taboption("main", ListValue, "global_server", "TCP Server")
o:value("nil", "Disable")
for _, key in pairs(key_table) do o:value(key, server_table[key]) end
o.default = "nil"
o.rmempty = false
o = s:taboption("main", ListValue, "udp_relay_server", "UDP Server")
o:value("same", "Same as TCP Server")
for _, key in pairs(key_table) do o:value(key, server_table[key]) end
o.default = "nil"
o = s:taboption("main", ListValue, "socks5_server", "Socks5 Server")
o:value("nil", "Disable")
o:value("same", "Same as TCP Server")
for _, key in pairs(key_table) do o:value(key, server_table[key]) end
o.default = "nil"
o = s:taboption("settings", ListValue, 'log_level')
o.title = "Log Level"
o:value("debug", "Debug")
o:value("info", "Info")
o:value("warning", "Warning")
o:value("error", "Error")
o:value("none", "None")
o.default = "info"
o = s:taboption("settings", ListValue, "pdnsd_enable", "DNS Resolver")
o:value("1", "PDNSD")
o:value("2", "DNS2SOCKS")
o.default = 1
o = s:taboption("settings", Value, "tunnel_forward", "DNS Server")
o:value("8.8.4.4:53", "Google Public DNS (8.8.4.4:53)")
o:value("8.8.8.8:53", "Google Public DNS (8.8.8.8:53)")
o:value("208.67.222.222:53", "OpenDNS (208.67.222.222:53)")
o:value("208.67.220.220:53", "OpenDNS (208.67.220.220:53)")
o:value("209.244.0.3:53", "Level 3 Public DNS (209.244.0.3:53)")
o:value("209.244.0.4:53", "Level 3 Public DNS (209.244.0.4:53)")
o:value("4.2.2.1:53", "Level 3 Public DNS (4.2.2.1:53)")
o:value("4.2.2.2:53", "Level 3 Public DNS (4.2.2.2:53)")
o:value("4.2.2.3:53", "Level 3 Public DNS (4.2.2.3:53)")
o:value("4.2.2.4:53", "Level 3 Public DNS (4.2.2.4:53)")
o:value("1.1.1.1:53", "Cloudflare DNS (1.1.1.1:53)")
o:depends("pdnsd_enable", "1")
o:depends("pdnsd_enable", "2")
o.datatype = "hostport"
o = s:taboption('access', ListValue, 'lan_ifaces')
o.title = "LAN Interface"
o:value("br-lan", "br-lan")
for _, e in ipairs(luci.sys.net.devices()) do if e ~= "lo" then o:value(e) end end
o.default="br-lan"
o.rmempty = false
o = s:taboption("access", ListValue, "lan_ac_mode", "LAN Access Control")
o:value("1", "Bypassed List Mode")
o:value("2", "Proxied List Mode")
o = s:taboption("access", DynamicList, "lan_fp_ips", "LAN Proxied Host List")
o.datatype = "ipaddr"
luci.ip.neighbors({family = 4}, function(entry)
if entry.reachable then
o:value(entry.dest:string())
end
end)
o:depends("lan_ac_mode","2")
o = s:taboption("access", DynamicList, "lan_bp_ips", "LAN Bypassed Host List")
o.datatype = "ipaddr"
luci.ip.neighbors({family = 4}, function(entry)
if entry.reachable then
o:value(entry.dest:string())
end
end)
o:depends("lan_ac_mode","1")
o = s:taboption("access", DynamicList, "wan_bp_ips", "WAN Bypassed Host List")
o.datatype = "ip4addr"
o = s:taboption("ports", Value, "tcp_port", "TCP Port")
o.datatype = "port"
o.default = 1234
o.rmempty = false
o = s:taboption("ports", Value, "udp_port", "UDP Port")
o.datatype = "port"
o.default = 5350
o.rmempty = false
o = s:taboption("ports", Value, "socks5_port", translate("Socks5 Port"))
o.datatype = "port"
o.default = 1081
o.rmempty = false
o = s:taboption("ports", Value, "dns2socks_port", translate("DNS2Socks Port"))
o.datatype = "port"
o.default = 5351
o.rmempty = false
local apply = luci.http.formvalue("cbi.apply")
if apply then
m.uci:commit("xclient")
luci.sys.call("/etc/init.d/xclient boot >/dev/null 2>&1 &")
end
return m
@@ -0,0 +1,10 @@
m = Map("xclient")
s = m:section(TypedSection, "xclient")
s.anonymous = true
s.addremove=false
s:append(Template("xclient/version"))
return m
@@ -0,0 +1,105 @@
<%+cbi/valueheader%>
<script type="text/javascript">//<![CDATA[
function account(btn,option)
{
btn.disabled = true;
if (option == "login") {
var s = document.getElementById(option+'-status');
var e = document.getElementsByName('cbid.xclient.config.email');
var p = document.getElementsByName('cbid.xclient.config.passwd');
var u = document.getElementsByName('cbid.xclient.config.site');
if (!u[0].value || !e[0].value || !p[0].value) {
btn.disabled = false;
s.innerHTML ="<font color='red'><strong>Missing field</strong></font>";
return false;
};
btn.value = 'Login...';
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "xclient", "login_info")%>', {weburl: u[0].value, email: e[0].value, passwd : p[0].value}, function(x, status) {
if (x && x.status == 200) {
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "xclient", "login")%>', null, function(x, status) {
if (s)
{
if (x && x.status == 200 && status.login == 1) {
s.innerHTML ="<font color='green'><strong>Login Successful</strong></font>";
document.getElementById('logout').hidden = false;
document.getElementById('update').hidden = false;
document.getElementById('login').hidden = true;
subscribe();
}
else {
s.innerHTML ="<font color='red'><strong>Login Failed</strong></font>";
}
}
btn.disabled = false;
btn.value = 'Login To Account';
});
}
});
};
if (option == "logout") {
var s = document.getElementById('login-status');
btn.value = '<%:Logout...%>';
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "xclient", "logout")%>', null, function(x, status) {
if (s)
{
if (x && x.status == 200 && status.logout == 1) {
s.innerHTML ="<font color='green'><strong>Logout Successful</strong></font>";
document.getElementById('logout').hidden = true;
document.getElementById('update').hidden = true;
document.getElementById('login').hidden = false;
}
else {
s.innerHTML ="<font color='red'><strong>Logout Failed</strong></font>";
}
}
btn.disabled = false;
btn.value = 'Logout Account';
}
);
};
return false;
}
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "xclient","login_check")%>', null, function(x,status) {
if (x && x.status == 200 && status.login == 1) {
document.getElementById('login').hidden = true;
document.getElementById('logout').hidden = false;
document.getElementById('update').hidden = false;
}
});
function subscribe()
{
var s = document.getElementById('login-status');
document.getElementById('update').disabled = true;
document.getElementById('update').value = 'Updating Servers...';
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "xclient","subscribe")%>', null, function(x,status) {
if (x && x.status == 200 && status.ret == 1) {
s.innerHTML ="<font color='green'><strong>Upate Servers Successful</strong></font>";
document.getElementById('update').disabled = false;
document.getElementById('update').value = 'Update Servers';
window.setTimeout("location.reload()", 2000);
}else{
s.innerHTML ="<font color='red'><strong>Error Upating Servers</strong></font>";
document.getElementById('update').disabled = false;
document.getElementById('update').value = 'Update Servers';
}
});
return false;
}
//]]></script>
<input type="button" id="login" class="btn cbi-button cbi-button-apply" value="Login To Account" onclick="return account(this,'login')" />
<input hidden id="logout" type="button" class="btn cbi-button cbi-button-remove" value="Logout Account" onclick="return account(this,'logout')" />
<input hidden id="update" type="button" class="btn cbi-button cbi-button-apply" value="Update Servers" onclick="return subscribe()" />
<br/><span id="<%=self.option%>-status"><%=self.value%></span>
<%+cbi/valuefooter%>
@@ -0,0 +1,92 @@
<%+cbi/valueheader%>
<script type="text/javascript">//<![CDATA[
function account(btn,option)
{
btn.disabled = true;
if (option == "login") {
var s = document.getElementById(option+'-status');
var e = document.getElementsByName('cbid.xclient.config.email');
var p = document.getElementsByName('cbid.xclient.config.passwd');
if (!e[0].value || !p[0].value) {
btn.disabled = false;
s.innerHTML ="<font color='red'><strong>Error Login Info</strong></font>";
return false;
};
btn.value = 'Login...';
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "xclient", "login_info")%>', {email: e[0].value, passwd : p[0].value}, function(x, status) {
if (x && x.status == 200) {
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "xclient", "login")%>', null, function(x, status) {
if (s)
{
if (x && x.status == 200 && status.login == 1) {
s.innerHTML ="<font color='green'><strong>Login Successful</strong></font>";
document.getElementById('logout').hidden = false;
document.getElementById('update').hidden = false;
document.getElementById('login').hidden = true;
subscribe();
}
else {
s.innerHTML ="<font color='red'><strong>Login Failed</strong></font>";
}
}
btn.disabled = false;
btn.value = 'Login To Account';
});
}
});
};
if (option == "logout") {
var s = document.getElementById('login-status');
btn.value = 'Logout..';
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "xclient", "logout")%>', null, function(x, status) {
if (s)
{
if (x && x.status == 200 && status.logout == 1) {
s.innerHTML ="<font color='green'><strong>Logout Successful</strong></font>";
document.getElementById('logout').hidden = true;
document.getElementById('update').hidden = true;
document.getElementById('login').hidden = false;
window.setTimeout("location.reload()", 2000);
}
else {
s.innerHTML ="<font color='red'><strong>Logout Failed</strong></font>";
}
}
btn.disabled = false;
btn.value = 'Logout Account';
}
);
};
return false;
}
function subscribe()
{
var s = document.getElementById('login-status');
document.getElementById('update').disabled = true;
document.getElementById('update').value = 'Updating Servers...';
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "xclient","subscribe")%>', null, function(x,status) {
if (x && x.status == 200 && status.ret == 1) {
s.innerHTML ="<font color='green'><strong>Upate Servers Successful</strong></font>";
document.getElementById('update').disabled = false;
document.getElementById('update').value = 'Update Servers';
window.setTimeout("location.reload()", 2000);
}else{
s.innerHTML ="<font color='red'><strong>Error Upating Servers</strong></font>";
document.getElementById('update').disabled = false;
document.getElementById('update').value = 'Update Servers';
}
});
return false;
}
//]]></script>
<input hidden type="button" id="login" class="btn cbi-button cbi-button-apply" value="Login To Account" onclick="return account(this,'login')" />
<input id="logout" type="button" class="btn cbi-button cbi-button-remove" value="Logout Account" onclick="return account(this,'logout')" />
<input id="update" type="button" class="btn cbi-button cbi-button-apply" value="Update Servers" onclick="return subscribe()" />
<br/><span id="<%=self.option%>-status"><%=self.value%></span>
<%+cbi/valuefooter%>
@@ -0,0 +1,21 @@
<%+cbi/valueheader%>
<textarea id="log" class="cbi-input-textarea" style="width: 100%;" data-update="change" rows="29" readonly="readonly" > </textarea>
<script type="text/javascript">//<![CDATA[
var lv = document.getElementById('log');
lv.style.display="inline"
XHR.poll(1, '<%=url([[admin]], [[services]], [[xclient]], [[logstatus]])%>', null,
function(x, data) {
var lv = document.getElementById('log');
if (x.responseText && lv) {
lv.innerHTML += x.responseText;
lv.scrollTop = lv.scrollHeight;
}
}
);
//]]>
</script>
<%+cbi/valuefooter%>
@@ -0,0 +1,3 @@
<%+cbi/valueheader%>
<span class="pingtime" hint="<%=self:cfgvalue(section)%>">Checking...</span>
<%+cbi/valuefooter%>
@@ -0,0 +1,143 @@
<script type="text/javascript">
//<![CDATA[
window.addEventListener('load',function(){
const doms = document.getElementsByClassName('pingtime');
const ports = document.getElementsByClassName("socket-connected");
const transports = document.getElementsByClassName("transport");
const wsPaths = document.getElementsByClassName("wsPath");
const tlss = document.getElementsByClassName("tls");
const xhr = (index) => {
return new Promise((res) => {
const dom = doms[index];
const port = ports[index];
const transport = transports[index];
const wsPath = wsPaths[index];
const tls = tlss[index];
if (!dom) res()
port.innerHTML = '<font color="#0072c3">Checking...</font>';
XHR.get('<%=luci.dispatcher.build_url("admin/services/xclient/ping")%>', {
index,
domain: dom.getAttribute("hint"),
port: port.getAttribute("hint"),
transport: transport.getAttribute("hint"),
wsPath: wsPath.getAttribute("hint"),
tls: tls.getAttribute("hint")
},
(x, result) => {
let col = '#ff0000';
if (result.ping) {
if (result.ping < 300) col = '#eb5949';
if (result.ping < 250) col = '#e1ff4a';
if (result.ping < 200) col = '#91fc62';
if (result.ping < 100) col = '#4aff62';
if (result.ping < 50) col = '#16a142';
}
dom.innerHTML = `<font color="${col}">${(result.ping ? result.ping : "--") + " ms"}</font>`
if (result.socket) {
port.innerHTML = '<font color="#249400">NORMAL</font>';
} else {
port.innerHTML = '<font color="#ff0000">ERROR</font>';
}
res();
});
})
}
let task = -1;
const thread = () => {
task = task + 1
if (doms[task]) {
xhr(task).then(thread);
}
}
for (let i = 0; i < 20; i++) {
thread()
}
})
function cbi_row_drop(fromId, toId, store, isToBottom) {
var fromNode = document.getElementById(fromId);
var toNode = document.getElementById(toId);
if (!fromNode || !toNode) return false;
var table = fromNode.parentNode;
while (table && table.nodeName.toLowerCase() != "table")
table = table.parentNode;
if (!table) return false;
var ids = [];
if (isToBottom) {
toNode.parentNode.appendChild(fromNode);
} else {
fromNode.parentNode.insertBefore(fromNode, toNode);
}
for (var idx = 2; idx < table.rows.length; idx++) {
table.rows[idx].className = table.rows[idx].className.replace(
/cbi-rowstyle-[12]/,
"cbi-rowstyle-" + (1 + (idx % 2))
);
if (table.rows[idx].id && table.rows[idx].id.match(/-([^\-]+)$/))
ids.push(RegExp.$1);
}
var input = document.getElementById(store);
if (input) input.value = ids.join(" ");
return false;
}
// set tr draggable
function enableDragForTable(table_selecter, store) {
var trs = document.querySelectorAll(table_selecter + " tr");
if (!trs || trs.length.length < 3) {
return;
}
function ondragstart(ev) {
ev.dataTransfer.setData("Text", ev.target.id);
}
function ondrop(ev) {
var from = ev.dataTransfer.getData("Text");
cbi_row_drop(from, this.id, store);
}
function ondragover(ev) {
ev.preventDefault();
ev.dataTransfer.dropEffect = "move";
}
function moveToTop(id) {
var top = document.querySelectorAll(table_selecter + " tr")[2];
cbi_row_drop(id, top.id, store);
}
function moveToBottom(id) {
console.log('moveToBottom:', id);
var trList = document.querySelectorAll(table_selecter + " tr");
var bottom = trList[trList.length - 1];
cbi_row_drop(id, bottom.id, store, true);
}
for (let index = 2; index < trs.length; index++) {
const el = trs[index];
el.setAttribute("draggable", true);
el.ondragstart = ondragstart;
el.ondrop = ondrop;
el.ondragover = ondragover;
// reset the behaviors of the btns
var upBtns = el.querySelectorAll(".cbi-button.cbi-button-up");
if (upBtns && upBtns.length > 0) {
upBtns.forEach(function (_el) {
_el.onclick = function () {
moveToTop(el.id);
};
});
}
var downBtns = el.querySelectorAll(".cbi-button.cbi-button-down");
if (downBtns && downBtns.length > 0) {
downBtns.forEach(function (_el) {
_el.onclick = function () {
moveToBottom(el.id);
};
});
}
}
}
// enable
enableDragForTable(
"#cbi-xclient-servers table",
"cbi.sts.xclient.servers"
);
</script>
@@ -0,0 +1,6 @@
<%+cbi/valueheader%>
<span class="socket-connected" hint="<%=self:cfgvalue(section)%>">Checking...</span>
<span class="transport" hint="<%=self.transport%>"></span>
<span class="wsPath" hint="<%=self.ws_path%>"></span>
<span class="tls" hint="<%=self.tls%>"></span>
<%+cbi/valuefooter%>
@@ -0,0 +1,446 @@
<style>
.pure-g{
letter-spacing:-.31em;
text-rendering:optimizespeed;
font-family:FreeSans,Arimo,
"Droid Sans",
Helvetica,
Arial,s
ans-serif;
display:-webkit-box;
display:-webkit-flex;
display:-ms-flexbox;
display:flex;
-webkit-box-orient:horizontal;
-webkit-box-direction:normal;
-webkit-flex-flow:row wrap;
-ms-flex-flow:row wrap;
flex-flow:row wrap;
-webkit-align-content:flex-start;
-ms-flex-line-pack:start;
align-content:flex-start
}
@media all and (-ms-high-contrast:none),
(-ms-high-contrast:active){
table .pure-g{display:block}
}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{
display:inline-block;
zoom:1;
letter-spacing:normal;
word-spacing:normal;
vertical-align:top;
text-rendering:auto
}.pure-g [class*=pure-u]{font-family:sans-serif}
.pure-u-1,
.pure-u-1-1,
.pure-u-1-12,
.pure-u-1-2,
.pure-u-1-24,
.pure-u-1-3,
.pure-u-1-4,
.pure-u-1-5,
.pure-u-1-6,
.pure-u-1-8,
.pure-u-10-24,
.pure-u-11-12,
.pure-u-11-24,
.pure-u-12-24,
.pure-u-13-24,
.pure-u-14-24,
.pure-u-15-24,
.pure-u-16-24,
.pure-u-17-24,
.pure-u-18-24,
.pure-u-19-24,
.pure-u-2-24,
.pure-u-2-3,
.pure-u-2-5,
.pure-u-20-24,
.pure-u-21-24,
.pure-u-22-24,
.pure-u-23-24,
.pure-u-24-24,
.pure-u-3-24,
.pure-u-3-4,
.pure-u-3-5,
.pure-u-3-8,
.pure-u-4-24,
.pure-u-4-5,
.pure-u-5-12,
.pure-u-5-24,
.pure-u-5-5,
.pure-u-5-6,
.pure-u-5-8,
.pure-u-6-24,
.pure-u-7-12,
.pure-u-7-24,
.pure-u-7-8,
.pure-u-8-24,
.pure-u-9-24{
display:inline-block;
zoom:1;
letter-spacing:normal;
word-spacing:normal;
vertical-align:top;
text-rendering:auto
}
.pure-u-1-24{width:4.1667%}.pure-u-1-12,.pure-u-2-24{width:8.3333%}.pure-u-1-8,.pure-u-3-24{width:12.5%}.pure-u-1-6,.pure-u-4-24{width:16.6667%}.pure-u-1-5{width:20%}.pure-u-5-24{width:20.8333%}.pure-u-1-4,.pure-u-6-24{width:25%}.pure-u-7-24{width:29.1667%}.pure-u-1-3,.pure-u-8-24{width:33.3333%}.pure-u-3-8,.pure-u-9-24{width:37.5%}.pure-u-2-5{width:40%}.pure-u-10-24,.pure-u-5-12{width:41.6667%}.pure-u-11-24{width:45.8333%}.pure-u-1-2,.pure-u-12-24{width:50%}.pure-u-13-24{width:54.1667%}.pure-u-14-24,.pure-u-7-12{width:58.3333%}.pure-u-3-5{width:60%}.pure-u-15-24,.pure-u-5-8{width:62.5%}.pure-u-16-24,.pure-u-2-3{width:66.6667%}.pure-u-17-24{width:70.8333%}.pure-u-18-24,.pure-u-3-4{width:75%}.pure-u-19-24{width:79.1667%}.pure-u-4-5{width:80%}.pure-u-20-24,.pure-u-5-6{width:83.3333%}.pure-u-21-24,.pure-u-7-8{width:87.5%}.pure-u-11-12,.pure-u-22-24{width:91.6667%}.pure-u-23-24{width:95.8333%}.pure-u-1,.pure-u-1-1,.pure-u-24-24,.pure-u-5-5{width:100%}
.block{
margin: 0.5rem;
padding: 0;
font-weight: normal;
font-style: normal;
line-height: 1;
font-family: inherit;
min-width: inherit;
overflow-x: auto;
overflow-y: hidden;
border: 1px solid rgba(0,0,0,.05);
border-radius: .375rem;
background-color: #fff;
box-shadow: 0 0 2rem 0 rgba(136,152,170,.15);
}
.img-con{
margin: 1rem;
}
.green{
font-size:.9rem;
color: #2dce89;
}
.red{
font-size:.9rem;
color: #fb6340;
}
.yellow{
font-size:.9rem;
color: #fb9a05;
}
.block img{
width: 50px;
height: auto;
}
.pure-u-5-8{
display:flex;
align-items:center;
}
.block h4{
font-size: .8125rem;
font-weight: 600;
margin: 1rem;
color:#8898aa!important;
line-height: 1.8em;
min-height: 48px;
}
.check {
cursor: pointer;
}
@media screen and (max-width: 720px) {
.block{
margin: 0.2rem;
}
.pure-u-1-4{
width: 50%;
}
.pure-u-1-2{
width: 100%;
}
}
@@media screen and (max-width: 480px) {
.img-con {
margin: 1.5rem 0.5rem;
}
.block img {
width: 36px;
}
}
@media only screen and (max-width: 600px) {
.siz {
width: 100%;
}
}
@media only screen and (min-width: 768px) {
.siz {
width: 100%;
}
}
@media only screen and (min-width: 600px) {
.siz {
width: 100%;
}
}
@media only screen and (max-width: 480px) {
.siz {
width: 100%;
}
}
@media only screen and (min-width: 600px) {
.sizz {
width: 33.33%;
}
}
@media only screen and (max-width: 480px) {
.sizz {
width: 100%;
}
}
@media only screen and (max-width: 600px) {
.sizz {
width: 100%;
}
}
</style>
<fieldset class="cbi-section">
<div class="pure-g status">
<div class="pure-u-1-4">
<div class="block pure-g">
<div class="pure-u-1-3">
<div class="img-con">
<img src="/luci-static/xclient/img/site_icon1_01.png" style="width:60px;heiht:100%" class="baidu">
</div>
</div>
<div class="pure-u-2-3">
<h4>Baidu<br/><span id="status_baidu" class="red">Checking...</span></h4>
</div>
</div>
</div>
<div class="pure-u-1-4" >
<div class="block pure-g">
<div class="pure-u-1-3">
<div class="img-con">
<img src="/luci-static/xclient/img/site_icon1_02.png" style="width:60px;heiht:100%" class="taobao">
</div>
</div>
<div class="pure-u-2-3">
<h4 >Taobao<br/><span id="status_taobao" class="red">Checking...</span></h4>
</div>
</div>
</div>
<div class="pure-u-1-4 " >
<div class="block pure-g">
<div class="pure-u-1-3">
<div class="img-con">
<img src="/luci-static/xclient/img/site_icon1_03.png" style="width:60px;heiht:100%" class="google">
</div>
</div>
<div class="pure-u-2-3">
<h4>Google<br/><span id="status_google" class="red">Checking...</span></h4>
</div>
</div>
</div>
<div class="pure-u-1-4 ">
<div class="block pure-g">
<div class="pure-u-1-3">
<div class="img-con">
<img src="/luci-static/xclient/img/site_icon1_04.png" style="width:60px;heiht:100%" class="youtube">
</div>
</div>
<div class="pure-u-2-3">
<h4 >Youtube<br/><span id="status_youtube" class="red">Checking...</span></h4>
</div>
</div>
</div>
<div class="pure-u-1-4 ">
<div class="block pure-g">
<div class="pure-u-9-24">
<div class="img-con">
<img src="data:image/png;base64,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 truncated
</div>
</div>
<div class="pure-u-14-24">
<h4>TCP<br/><span id="_tcp" class="red">Checking...</span></h4>
</div>
</div>
</div>
<div class="pure-u-1-4 ">
<div class="block pure-g">
<div class="pure-u-9-24">
<div class="img-con">
<img src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjEsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0i5Zu+5bGCXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTAwIDEwMDsiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4NCgkuc3Qwe2ZpbGw6I0ZCQjE0MDt9DQoJLnN0MXtmaWxsOiNGRkZGRkY7fQ0KPC9zdHlsZT4NCjxjaXJjbGUgY2xhc3M9InN0MCIgY3g9IjUwIiBjeT0iNTAiIHI9IjUwIi8+DQo8cGF0aCBjbGFzcz0ic3QxIiBkPSJNODYuOCw0Ny42QzgzLjYsMzMsNzQuOCwyMi41LDY2LjUsMjNoLTAuM2MtMy4zLDAtNi41LDEuMi04LjksMy4zYy0xLjgsMi00LjQsMy4yLTcuMSwzLjINCgljLTIuNiwwLTUuMS0xLjEtNi45LTNjLTIuNS0yLjMtNS44LTMuNS05LjEtMy41aC0wLjRjLTguMi0wLjUtMTcuMSwxMC0yMC4zLDI0LjZjLTMuNSwxNS41LDAuOCwyOS44LDkuNSwzMS45DQoJYzYuMywxLjYsMTMuMy0zLjksMTcuOS0xM2gxOC43YzQuNyw5LjEsMTEuNiwxNC42LDE3LjksMTNDODYsNzcuNCw5MC4yLDYzLjEsODYuOCw0Ny42TDg2LjgsNDcuNnogTTQyLjksNDguNUgzNnY3LjJoLTUuNnYtNy4yDQoJaC02Ljl2LTUuOGg2Ljl2LTcuMkgzNnY3LjJoNi45TDQyLjksNDguNXogTTYyLjcsNDkuMmMtMiwwLjEtMy43LTEuNC0zLjgtMy40Yy0wLjEtMiwxLjQtMy43LDMuNC0zLjhjMi0wLjEsMy43LDEuNCwzLjgsMy40DQoJYzAsMC4xLDAsMC4xLDAsMC4yQzY2LjIsNDcuNSw2NC43LDQ5LjEsNjIuNyw0OS4yQzYyLjcsNDkuMiw2Mi43LDQ5LjIsNjIuNyw0OS4yeiBNNzMuNyw0OS4yYy0yLDAuMS0zLjctMS40LTMuOC0zLjQNCgljLTAuMS0yLDEuNC0zLjcsMy40LTMuOGMyLTAuMSwzLjcsMS40LDMuOCwzLjRjMCwwLjEsMCwwLjEsMCwwLjJjMC4xLDEuOS0xLjQsMy41LTMuMywzLjZINzMuN3oiLz4NCjwvc3ZnPg0K" />
</div>
</div>
<div class="pure-u-14-24">
<h4>UDP<br/><span id="_udp" class="red">Checking...</span></h4>
</div>
</div>
</div>
<div class="pure-u-1-4" >
<div class="block pure-g">
<div class="pure-u-9-24">
<div class="img-con">
<img src="data:image/png;base64,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 truncated
"/>
</div>
</div>
<div class="pure-u-14-24">
<h4 >DNS<br/><span id="_dns" class="red">Checking...</span></h4>
</div>
</div>
</div>
<div class="pure-u-1-4 " >
<div class="block pure-g">
<div class="pure-u-9-24">
<div class="img-con">
<img src="data:image/png;base64,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 truncated
</div>
</div>
<div class="pure-u-14-24">
<h4>Socks5 Server<br/><span id="_socks" class="red">Checking...</span></h4>
</div>
</div>
</div>
</div>
</fieldset>
<!--<fieldset class="cbi-section">
<div class="pure-u-24-24" >
<div class="block pure-g">
<div class="pure-u-2-24">
<div class="img-con">
<img src="/luci-static/xclient/img/loading.svg" style="width:60px;heiht:100%">
</div>
</div>
<div class="pure-u-22-24">
<h4><b>GEOLOCATION </b><br ><span id="ip" >Checking...</span></h4>
</div>
</div>
</div>
</fieldset>-->
<fieldset class="cbi-section">
<table width="100%" cellspacing="10">
<tr><td width="25%" ><span></span></td ><td width="30%" ><b><span> ↓ </span> <span id="_speed_download">0 B/s</span></b></td > <td ><b><span> ↑ </span> <span id="_speed_upload">0 B/s</span></b></td></tr>
</table>
</fieldset>
<script type="text/javascript">//<![CDATA[
const _IMG = '/luci-static/xclient/';
var xclient = document.getElementById('_xclient');
var dns = document.getElementById('_dns');
var tcp = document.getElementById('_tcp');
var udp = document.getElementById('_udp');
var socks = document.getElementById('_socks');
function state(status) {
if(status.dns_status == "1"){
dns.innerHTML = '<b class="green">RUNNING</b>';
}else{
dns.innerHTML = '<b class="red">NOT RUNNING</b>';
}
if(status.tcp_status == "1"){
tcp.innerHTML = '<b class="green">RUNNING</b>';
}else{
tcp.innerHTML = '<b class="red">NOT RUNNING</b>';
}
if(status.udp_status == "1"){
udp.innerHTML = '<b class="green">RUNNING</b>';
}else{
udp.innerHTML = '<b class="red">NOT RUNNING</b>';
}
if(status.socks_status == "1"){
socks.innerHTML = '<b class="green">RUNNING</b>';
}else{
socks.innerHTML = '<b class="red">NOT RUNNING</b>';
}
}
XHR.poll(1, '<%=luci.dispatcher.build_url("admin", "services", "xclient", "run")%>', null, function(x, status) {
state(status);
}
);
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "xclient", "run")%>', null, function(x, status) {
state(status);
}
);
//
//XHR.get('<%=luci.dispatcher.build_url("admin", "services", "xclient", "geolocation")%>', null, function(x, status) {
// if(x && x.status == 200) {
// document.getElementById('ip').innerHTML = "<span class='green'>" + status.location + "</span>";
// }
// }
//);
//XHR.poll(3,'<%=luci.dispatcher.build_url("admin", "services", "xclient", "geolocation")%>', null, function(x, status) {
// if(x && x.status == 200) {
// document.getElementById('ip').innerHTML = "<span class='green'>" + status.location + "</span>";
// }
// }
//);
//
function wirte(status) {
status.baidu ? $(".baidu").attr("src", _IMG + "img/site_icon_01.png") : $(".baidu").attr("src", _IMG + "img/site_icon1_01.png");
status.baidu ? document.getElementById('status_baidu').innerHTML = '<b class="green">NORMAL</b>' : document.getElementById('status_baidu').innerHTML = '<b class="red">NO ACCESS</b>';
status.taobao ? $(".taobao").attr("src", _IMG + "img/site_icon_02.png") : $(".taobao").attr("src", _IMG + "img/site_icon1_02.png");
status.taobao ? document.getElementById('status_taobao').innerHTML = '<b class="green">NORMAL</b>' : document.getElementById('status_taobao').innerHTML = '<b class="red">NO ACCESS</b>';
status.google ? $(".google").attr("src", _IMG + "img/site_icon_03.png") : $(".google").attr("src", _IMG + "img/site_icon1_03.png");
status.google ? document.getElementById('status_google').innerHTML = '<b class="green">NORMAL</b>' : document.getElementById('status_google').innerHTML = '<b class="red">NO ACCESS</b>';
status.youtube ? $(".youtube").attr("src", _IMG + "img/site_icon_04.png") : $(".youtube").attr("src", _IMG + "img/site_icon1_04.png");
status.youtube ? document.getElementById('status_youtube').innerHTML = '<b class="green">NORMAL</b>' : document.getElementById('status_youtube').innerHTML = '<b class="red">NO ACCESS</b>';
}
XHR.poll(3, '<%=luci.dispatcher.build_url("admin", "services", "xclient", "web")%>', null, function(x, status) {
wirte(status);
}
);
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "xclient", "web")%>', null, function(x, status) {
wirte(status);
}
);
XHR.poll(2, '<%=luci.dispatcher.build_url("admin", "services", "xclient", "traffic")%>', null, function(x, status)
{
traffic(status.downlink, status.uplink);
});
function traffic(downlink, uplink) {
var uplspd = stats(uplink);
var dnlspd = stats(downlink);
document.getElementById('_speed_upload').innerHTML = uplspd;
document.getElementById('_speed_download').innerHTML = dnlspd;
}
function stats(value) {
if (value > 1099511627776) {
return Math.round(value/1099511627776) + " TB/s"
} else if (value > 1073741824) {
return Math.round(value/1073741824) + " GB/s"
} else if (value > 1048576) {
return Math.round(value/1048576) + " MB/s"
} else if( value > 1024) {
return Math.round(value/1024) + " KB/s"
} else {
return Math.round(value) + " B/s"
}
}
//]]></script>
@@ -0,0 +1,184 @@
<%local fs=require"nixio.fs"%>
<fieldset class="cbi-section">
<table width="100%" cellspacing="10">
<thead align="center">
<tr>
<th align="center">Name</th>
<th align="center">Version</th>
<th align="center">Latest</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center" width="30%">Xray-Core</td> <td align="center" width="35%" id="_core">Checkin...</td> <td width="35%" align="center" id="_newcore"><font color="red">Checkin...</font> </td>
</tr>
<tr><td></td><td></td><td></td></tr>
</tbody>
</table>
</fieldset>
<fieldset class="cbi-section">
<table width="100%" cellspacing="10">
<thead align="center">
<tr>
<th align="center">Name</th>
<th align="center">Version</th>
<th align="center">Update</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center" width="30%">geoip file</td> <td align="center" width="35%" id="_geoip">Checkin...</td> <td width="35%" align="center" id="_newgeoip"><font color="red">Checkin...</font> </td>
</tr>
<tr>
<td align="center" width="30%">geosite file</td> <td align="center" width="35%" id="_geosite">Checkin...</td> <td width="35%" align="center" id="_newgeosite"><font color="red">Checkin...</font> </td>
</tr>
<tr><td></td><td></td><td></td></tr>
</tbody>
</table>
<textarea id="geoip_download" class="cbi-input-textarea" style="width: 100%;display:none" data-update="change" rows="20" cols="100" readonly="readonly" > </textarea>
<textarea id="geosite_download" class="cbi-input-textarea" style="width: 100%;display:none" data-update="change" rows="20" cols="100" readonly="readonly" > </textarea>
</fieldset>
<script type="text/javascript">//<![CDATA[
var _core = document.getElementById('_core');
var _geoip = document.getElementById('_geoip');
var _geosite = document.getElementById('_geosite');
var _newcore = document.getElementById('_newcore');
XHR.get('<%=url([[admin]], [[services]], [[xclient]], [[version]])%>', null, function(x, status) {
if ( x && x.status == 200) {
_core.innerHTML = status.core_version ? '<b><font color="green">' + status.core_version + '</font></b>' : '<b><font color="red">X</font> </b>';
_geoip.innerHTML = status.geoip_version ? '<b><font color="green">' + status.geoip_version + '</font></b>' : '<b><font color="red">X</font> </b>';
_geosite.innerHTML = status.geosite_version ? '<b><font color="green">' + status.geosite_version + '</font></b>' : '<b><font color="red">X</font> </b>';
_newcore.innerHTML = status.latest_core ? '<b><font color="red"><a href="https://github.com/XTLS/Xray-core/releases/tag/' + status.latest_core + '" target="_blank">' + status.latest_core + '</a></font></b>' : '<b>--</b>';
}
});
XHR.poll(1,'<%=url([[admin]], [[services]], [[xclient]], [[check_latest]])%>', null, function(x, status) {});
XHR.poll(3,'<%=url([[admin]], [[services]], [[xclient]], [[version]])%>', null, function(x, status) {
if ( x && x.status == 200) {
_core.innerHTML = status.core_version ? '<b><font color="green">' + status.core_version + '</font></b>' : '<b><font color="red">X</font> </b>';
_geoip.innerHTML = status.geoip_version ? '<b><font color="green">' + status.geoip_version + '</font></b>' : '<b><font color="red">X</font> </b>';
_geosite.innerHTML = status.geosite_version ? '<b><font color="green">' + status.geosite_version + '</font></b>' : '<b><font color="red">X</font> </b>';
_newcore.innerHTML = status.latest_core ? '<b><font color="red"><a href="https://github.com/XTLS/Xray-core/releases/tag/' + status.latest_core + '" target="_blank">' + status.latest_core + '</a></font></b>' : '<b>--</b>';
}
});
document.getElementById('_newgeoip').innerHTML = '<input type="button" class="cbi-button cbi-button-apply" id="apply_update_geoip" value="Manually Update" onclick=" return update_geoip() "/>';
document.getElementById('_newgeosite').innerHTML = '<input type="button" class="cbi-button cbi-button-apply" id="apply_update_geosite" value="Manually Update" onclick=" return update_geosite() "/>';
<% if not fs.access("/var/lock/xclient.lock") then %>
function update_geoip(){
XHR.get('<%=url([[admin]], [[services]], [[xclient]], [[geoipupdate]])%>',null,function(x, data){});
geoip_poll_check();
return
}
<%end%>
function geoip_poll_check(){
var lv = document.getElementById('geoip_download');
lv.style.display="inline"
XHR.poll(2, '<%=url([[admin]], [[services]], [[xclient]], [[check_geoip]])%>', null,
function(x, data) {
var lv = document.getElementById('geoip_download');
if (x.responseText && lv) {
lv.innerHTML += x.responseText;
lv.scrollTop = lv.scrollHeight;
}
}
);
}
XHR.poll(3, '<%=luci.dispatcher.build_url("admin", "services", "xclient", "geoip")%>', null, function(x, status) {
if ( x && x.status == 200 ) {
if(status.geoipcheck == 0||status.geoipcheck == 2){
hide_ip();
}
if(status.geoipcheck == 1){
var btn = document.getElementById('apply_update_geoip');
btn.disabled = false;
btn.value = 'Manually Update';
geoip_poll_check();
}
}
});
<% if not fs.access("/var/lock/xclient.lock") then %>
function update_geosite(){
XHR.get('<%=url([[admin]], [[services]], [[xclient]], [[geositeupdate]])%>',null,function(x, data){});
geosite_poll_check();
return
}
<%end%>
function geosite_poll_check(){
var lv = document.getElementById('geosite_download');
lv.style.display="inline"
XHR.poll(2, '<%=url([[admin]], [[services]], [[xclient]], [[check_geosite]])%>', null,
function(x, data) {
var lv = document.getElementById('geosite_download');
if (x.responseText && lv) {
lv.innerHTML += x.responseText;
lv.scrollTop = lv.scrollHeight;
}
}
);
}
XHR.poll(3, '<%=luci.dispatcher.build_url("admin", "services", "xclient", "geosite")%>', null, function(x, status) {
if ( x && x.status == 200 ) {
if(status.geositecheck == 0||status.geositecheck == 2){
hide_site();
}
if(status.geositecheck == 1){
var btn = document.getElementById('apply_update_geosite');
btn.disabled = false;
btn.value = 'Manually Update';
geosite_poll_check();
}
}
});
function hide_ip(){
$('#geoip_download').hide();
var btn = document.getElementById('apply_update_geoip');
btn.disabled = false;
btn.value = 'Manually Update';
}
function hide_site(){
$('#geosite_download').hide();
var btn = document.getElementById('apply_update_geosite');
btn.disabled = false;
btn.value = 'Manually Update';
}
<% if fs.access("/var/run/geo_ip_update") then %>
var btn = document.getElementById('apply_update_geoip');
btn.disabled = true;
btn.value = 'Downloading...';
geoip_poll_check();
<%end%>
<% if fs.access("/var/run/geo_site_update") then %>
var btn = document.getElementById('apply_update_geosite');
btn.disabled = true;
btn.value = 'Downloading...';
geosite_poll_check();
<%end%>
//]]></script>
+104
View File
@@ -0,0 +1,104 @@
config global
option monitor_enable '1'
option dports '1'
option pdnsd_enable '1'
option tunnel_forward '8.8.4.4:53'
list Interface 'lan'
option disableCache '0'
option disableFallback '0'
option disableFallbackIfMatch '0'
option queryStrategy 'UseIP'
option domainMatcher 'linear'
option routing_strategy 'IPIfNonMatch'
list dns_servers 'https+local://cloudflare-dns.com/dns-query'
list dns_servers 'https://1.1.1.1/dns-query'
list dns_servers 'https://dns.google/dns-query'
list dns_servers '1.1.1.1'
list dns_servers '8.8.8.8'
option tcp_port '1234'
option udp_port '5350'
option socks5_port '1081'
option dns2socks_port '5351'
option global_server 'nil'
option udp_relay_server 'same'
config xclient 'config'
config server_global
option enable_server '0'
config log
config update
config rule
option outboundTag 'block'
list inboundTag 'proxy_inbound'
list inboundTag 'dns_inbound'
list inboundTag 'socks_inbound'
option domainMatcher 'linear'
list domain 'geosite:category-ads-all'
list domain 'geosite:google-ads'
list domain 'geosite:category-ads'
list domain 'geosite:apple-ads'
list domain 'geosite:win-spy'
list domain 'geosite:win-extra'
config rule
list inboundTag 'proxy_inbound'
list inboundTag 'dns_inbound'
list inboundTag 'socks_inbound'
option outboundTag 'direct'
option domainMatcher 'linear'
list domain 'geosite:geolocation-cn'
list domain 'geosite:category-games@cn'
list domain 'geosite:cn'
list ip 'geoip:private'
list ip 'geoip:cn'
config rule
list inboundTag 'proxy_inbound'
list inboundTag 'dns_inbound'
list inboundTag 'socks_inbound'
option outboundTag 'proxy_outbound'
option domainMatcher 'linear'
list domain 'geosite:geolocation-!cn'
list domain 'geosite:google'
list domain 'geosite:gfw'
option network 'tcp,udp'
list ip 'geoip:!cn'
list ip 'geoip:google'
list ip 'geoip:facebook'
list ip 'geoip:telegram'
list ip 'geoip:twitter'
config dns
option address '114.114.114.114'
option port '53'
list expectIPs 'geoip:cn'
list domains 'geosite:cn'
list domains 'geosite:category-games@cn'
option skipFallback '1'
config dns
option address 'https://1.1.1.1/dns-query'
list domains 'geosite:geolocation-!cn'
list domains 'geosite:google'
list domains 'geosite:netflix'
list domains 'domain:netflix.com'
list domains 'domain:netflix.net'
list domains 'domain:nflximg.net'
list domains 'domain:nflxvideo.net'
list domains 'domain:nflxso.net'
list domains 'domain:nflxext.com'
list domains 'domain:hulu.com'
list domains 'domain:huluim.com'
list domains 'domain:hbonow.com'
list domains 'domain:hbogo.com'
list domains 'domain:hbo.com'
option port '53'
list expectIPs 'geoip:!cn'
option skipFallback '0'
+473
View File
@@ -0,0 +1,473 @@
#!/bin/sh /etc/rc.common
START=95
STOP=15
SERVICE_DAEMONIZE=1
NAME=xclient
LOCK_FILE=/var/lock/$NAME.lock
LOG_FILE=/var/log/$NAME.log
XRAY_LOG_FILE=/usr/share/$NAME/$NAME.txt
REAL_LOG=$XRAY_LOG_FILE
TMP_PATH=/var/etc/$NAME
TMP_BIN_PATH=$TMP_PATH/bin
TMP_DNSMASQ_PATH=/tmp/dnsmasq.d/dnsmasq-$NAME.d
XBIN=/usr/bin/xray
tcp_config_file=
enable_socks=0
udp_config_file=
local_config_file=
ARG_UDP=
dns_port="5335" #dns port
server_count=0
redir_tcp=0
redir_udp=0
local_enable=0
pdnsd_enable_flag=0
switch_server=$1
CRON_FILE=/etc/crontabs/root
EXTRA_COMMANDS='reset'
EXTRA_HELP=" reset Reset to default settings"
#extra_command "reset" "Reset to default settings"
PS="/bin/busybox ps"
uci_get_by_name() {
local ret=$(uci get $NAME.$1.$2 2>/dev/null)
echo ${ret:=$3}
}
uci_get_by_type() {
local ret=$(uci get $NAME.@$1[0].$2 2>/dev/null)
echo ${ret:=$3}
}
uci_set_by_name() {
uci set $NAME.$1.$2=$3 2>/dev/null
uci commit $NAME
}
uci_set_by_type() {
uci set $NAME.@$1[0].$2=$3 2>/dev/null
uci commit $NAME
}
uci_get_by_cfgid() {
local ret=$(uci show $NAME.@$1[0].$2 | awk -F '.' '{print $2}' 2>/dev/null)
echo ${ret:=$3}
}
get_host_ip() {
local host=$(uci_get_by_name $1 server)
local ip=$host
if [ -z "$(echo $host | grep -E "([0-9]{1,3}[\.]){3}[0-9]{1,3}")" ]; then
if [ "$host" == "${host#*:[0-9a-fA-F]}" ]; then
ip=$(resolveip -4 -t 3 $host | awk 'NR==1{print}')
[ -z "$ip" ] && ip=$(uclient-fetch -q -O- http://119.29.29.29/d?dn=$host | awk -F ';' '{print $1}')
fi
fi
[ -z "$ip" ] || uci_set_by_name $1 ip $ip
echo ${ip:="$(uci_get_by_name $1 ip "ERROR")"}
}
clean_log() {
local logsnum=$(cat $LOG_FILE 2>/dev/null | wc -l)
[ "$logsnum" -gt 1000 ] && {
echo "$(date "+%Y-%m-%d %H:%M:%S") The log file is too long, clear it!" >$LOG_FILE
}
}
echolog() {
local d="$(date "+%Y-%m-%d %H:%M:%S")"
echo -e "$d: $*" >>$LOG_FILE
}
add_cron() {
touch $CRON_FILE
sed -i '/xclient.log/d' $CRON_FILE
[ $(uci get xclient.config.auto_update_servers) > 0 ] && echo "0 */$(uci get xclient.config.auto_update_servers) * * * /usr/bin/lua /usr/share/xclient/subscribe.lua >$LOG_FILE" >>$CRON_FILE
crontab $CRON_FILE
}
del_cron() {
touch $CRON_FILE
sed -i '/xclient.log/d' $CRON_FILE
crontab $CRON_FILE
clean_log
}
set_lock() {
exec 1000>"$LOCK_FILE"
flock -xn 1000
}
unset_lock() {
flock -u 1000
rm -rf "$LOCK_FILE"
}
unlock() {
failcount=1
while [ "$failcount" -le 10 ]; do
if [ -f "$LOCK_FILE" ]; then
let "failcount++"
sleep 1s
[ "$failcount" -ge 10 ] && unset_lock
else
break
fi
done
}
_exit() {
local rc=$1
unset_lock
exit ${rc}
}
first_type() {
type -t -p "/bin/${1}" -p "${TMP_BIN_PATH}/${1}" -p "${1}" "$@" | head -n1
}
ln_start_bin() {
local file_func=${1}
local ln_name=${2}
shift 2
if [ "${file_func%%/*}" != "${file_func}" ]; then
[ ! -L "${file_func}" ] && {
ln -s "${file_func}" "${TMP_BIN_PATH}/${ln_name}" >/dev/null 2>&1
file_func="${TMP_BIN_PATH}/${ln_name}"
}
[ -x "${file_func}" ] || echolog "$(readlink ${file_func}) no execute permission,Unable to start:${file_func} $*"
fi
#echo "${file_func} $*" >&2
[ -x "${file_func}" ] || {
echolog "Can't find ${file_func},Unable to start..."
echolog "-----------end------------"
_exit 2
}
${file_func:-echolog " - ${ln_name}"} "$@" >/dev/null 2>&1 &
}
start_dns() {
local xclient_dns="$(uci_get_by_type global pdnsd_enable 0)"
local dnsstr="$(uci_get_by_type global tunnel_forward 8.8.4.4:53)"
local dnsserver=$(echo "$dnsstr" | awk -F ':' '{print $1}')
local dnsport=$(echo "$dnsstr" | awk -F ':' '{print $2}')
start_pdnsd() {
local usr_dns="$1"
local usr_port="$2"
if [ ! -f "$TMP_PATH/pdnsd/pdnsd.cache" ]; then
mkdir -p $TMP_PATH/pdnsd
touch $TMP_PATH/pdnsd/pdnsd.cache
chown -R nobody:nogroup $TMP_PATH/pdnsd
fi
cat <<-EOF >$TMP_PATH/pdnsd.conf
global{
perm_cache=1024;
cache_dir="$TMP_PATH/pdnsd";
pid_file="/var/run/pdnsd.pid";
run_as="nobody";
server_ip=127.0.0.1;
server_port=$dns_port;
status_ctl=on;
query_method=tcp_only;
min_ttl=1h;
max_ttl=1w;
timeout=10;
neg_domain_pol=on;
proc_limit=2;
procq_limit=8;
par_queries=1;
}
server{
label="xclient-usrdns";
ip=$usr_dns;
port=$usr_port;
timeout=6;
uptest=none;
interval=10m;
purge_cache=off;
}
EOF
ln_start_bin $(first_type pdnsd) pdnsd -c $TMP_PATH/pdnsd.conf
}
if [ "$xclient_dns" != "0" ]; then
local dns2socks_port=$(uci_get_by_type global dns2socks_port)
ipset add xclient_spec_wan_ac $dnsserver nomatch 2>/dev/null
case "$xclient_dns" in
1)
start_pdnsd $dnsserver $dnsport
pdnsd_enable_flag=1
;;
2)
ln_start_bin $(first_type microsocks) microsocks -i 127.0.0.1 -p $dns2socks_port xclient-dns
ln_start_bin $(first_type dns2socks) dns2socks 127.0.0.1:$dns2socks_port $dnsserver:$dnsport 127.0.0.1:$dns_port -q
pdnsd_enable_flag=2
;;
esac
fi
}
gen_config_file() {
case "$2" in
1)
local protocol="redir"
config_file=$tcp_config_file
;;
2)
local protocol="redir"
config_file=$udp_config_file
;;
4)
local protocol="socks"
config_file=$local_config_file
;;
esac
lua /usr/share/xclient/gen_config.lua $1 $mode $3 $4 $protocol $5 $s5>$config_file
sed -i 's/\\//g' $TMP_PATH/*-xclient-*.json
}
start_udp() {
redir_udp=1
local type=$(uci_get_by_name $UDP_RELAY_SERVER protocol)
local udp_port=$(uci_get_by_type global udp_port)
case "$type" in
shadowsocks-plugin)
gen_config_file $UDP_RELAY_SERVER 2 $udp_port 0 $type
ss_program="$(first_type sslocal ss-redir)"
ln_start_bin $ss_program ss-redir -c $udp_config_file > $XRAY_LOG_FILE 2>&1 &
echolog "UDP TPROXY Relay:$(get_name ss) Started!"
;;
shadowsocks|vless|vmess|trojan)
gen_config_file $UDP_RELAY_SERVER 2 $udp_port 0 $type
sed -i "2i\ \"stats\": {}," $udp_config_file 2>/dev/null
$XBIN -config $udp_config_file > $XRAY_LOG_FILE 2>&1 &
echolog "UDP TPROXY Relay:$($(first_type "xray" "xray") -version | head -1) Started!"
;;
esac
}
start_local() {
[ "$LOCAL_SERVER" = "nil" ] && return 1
local socks_port=$(uci_get_by_type global socks5_port)
local type=$(uci_get_by_name $LOCAL_SERVER protocol)
case "$type" in
shadowsocks-plugin)
gen_config_file $LOCAL_SERVER 4 $socks_port 0 $type
ss_program="$(first_type sslocal ss-local)"
ln_start_bin $ss_program ss-local -c $local_config_file > $XRAY_LOG_FILE 2>&1 &
echolog "Global_Socks5:$(get_name ss) Started!"
;;
shadowsocks|vless|vmess|trojan)
if [ "$_local" == "2" ]; then
gen_config_file $LOCAL_SERVER 4 0 $socks_port 1 $type
sed -i "2i\ \"stats\": {}," $local_config_file 2>/dev/null
$XBIN -config $local_config_file > $XRAY_LOG_FILE 2>&1 &
echolog "Global_Socks5:$($(first_type "xray" "xray") -version | head -1) Started!"
fi
;;
esac
local_enable=1
return 0
}
Start_Run() {
if [ "$_local" == "1" ]; then
local socks_port=$(uci_get_by_type global socks5_port)
tcp_config_file=$TMP_PATH/local-xclient-retcp.json
[ "$mode" == "tcp,udp" ] && tcp_config_file=$TMP_PATH/local-udp-xclient-retcp.json
fi
local type=$(uci_get_by_name $GLOBAL_SERVER protocol)
local tcp_port=$(uci_get_by_type global tcp_port)
s5=""
case "$type" in
shadowsocks-plugin)
gen_config_file $GLOBAL_SERVER 1 $tcp_port $socks_port 0 $type
ss_program="$(first_type sslocal ss-redir)"
ln_start_bin "$ss_program" ss-redir -c $tcp_config_file > $XRAY_LOG_FILE 2>&1 &
echolog "Main node:$(get_name $type) $threads Threads Started!"
;;
shadowsocks|vless|vmess|trojan)
gen_config_file $GLOBAL_SERVER 1 $tcp_port $socks_port 0 $type
sed -i "2i\ \"stats\": {}," $tcp_config_file 2>/dev/null
$XBIN -config $tcp_config_file > $XRAY_LOG_FILE 2>&1 &
echolog "Main node:$($(first_type xray xray) -version | head -1) Started!"
;;
esac
redir_tcp=1
return 0
}
load_config() {
if [ -z "$switch_server" ]; then
GLOBAL_SERVER=$(uci_get_by_type global global_server nil)
else
GLOBAL_SERVER=$switch_server
fi
LOCAL_SERVER=$(uci_get_by_type global socks5_server nil)
if [ "$GLOBAL_SERVER" == "nil" ]; then
mode="tcp,udp"
_local="2"
local_config_file=$TMP_PATH/tcp-udp-xclient-local.json
start_local
return 1
fi
UDP_RELAY_SERVER=$(uci_get_by_type global udp_relay_server nil)
tcp_config_file=$TMP_PATH/tcp-only-xclient-retcp.json
case "$UDP_RELAY_SERVER" in
nil)
mode="tcp"
;;
$GLOBAL_SERVER | same)
mode="tcp,udp"
tcp_config_file=$TMP_PATH/tcp-udp-xclient-retcp.json
ARG_UDP="-u"
UDP_RELAY_SERVER=$GLOBAL_SERVER
;;
*)
mode="udp"
udp_config_file=$TMP_PATH/udp-only-xclient-reudp.json
ARG_UDP="-U"
start_udp
mode="tcp"
;;
esac
case "$LOCAL_SERVER" in
nil)
_local="0"
;;
$GLOBAL_SERVER | same)
_local="1"
LOCAL_SERVER=$GLOBAL_SERVER
local_config_file=$TMP_PATH/tcp-udp-xclient-local.json
start_local
local_enable=0
;;
*)
_local="2"
s5="none"
local_config_file=$TMP_PATH/tcp-udp-xclient-local.json
start_local
;;
esac
return 0
}
check_server() {
ENABLE_SERVER=$(uci_get_by_type global global_server nil)
if [ "$ENABLE_SERVER" == "nil" ]; then
return 1
else
local STYPE=$(uci_get_by_name $ENABLE_SERVER type nil)
if [ "$STYPE" == "nil" ]; then
local CFGID=$(uci_get_by_cfgid servers type nil)
if [ "$CFGID" == "nil" ]; then
uci_set_by_type global global_server 'nil'
else
uci_set_by_type global global_server $CFGID
fi
/etc/init.d/xclient restart
fi
fi
}
start_monitor() {
if [ $(uci_get_by_type global monitor_enable 1) == "1" ]; then
let total_count=redir_tcp+redir_udp+pdnsd_enable_flag
if [ $total_count -gt 0 ]; then
service_start /usr/share/xclient/monitor.sh $redir_tcp $redir_udp $pdnsd_enable_flag
fi
fi
}
start_rules() {
local server=$(get_host_ip $GLOBAL_SERVER)
local tcp_port=$(uci_get_by_type global tcp_port)
if [ "$redir_udp" == "1" ]; then
local udp_server=$(get_host_ip $UDP_RELAY_SERVER)
local udp_port=udp_port=$(uci_get_by_type global udp_port)
fi
/usr/share/xclient/iptables.sh \
-s "$server" \
-l "$tcp_port" \
-S "$udp_server" \
-L "$udp_port" \
-b "$(uci_get_by_type global wan_bp_ips)" \
-B "$(uci_get_by_type global lan_bp_ips)" \
-p "$(uci_get_by_type global lan_fp_ips)" \
-m "$(uci_get_by_type global Interface)" \
$ARG_UDP
return $?
}
start() {
echo "Starting Client" > $REAL_LOG
set_lock
echolog "----------starting------------"
mkdir -p /var/run /var/lock /var/log /tmp/dnsmasq.d $TMP_BIN_PATH $TMP_DNSMASQ_PATH
echo "conf-dir=${TMP_DNSMASQ_PATH}" >"/tmp/dnsmasq.d/dnsmasq-xclient.conf"
if load_config; then
echo "Generating Client Config" > $REAL_LOG
Start_Run
echo "Setting up iptables rules" > $REAL_LOG
start_rules
echo "Enabling Dns" > $REAL_LOG
start_dns
add_cron
fi
echo "Restarting Dnsmasq " > $REAL_LOG
/etc/init.d/dnsmasq restart >/dev/null 2>&1
check_server
start_monitor
clean_log
echolog "-----------Running------------"
unset_lock
echo "XClient Started Successfully" >$REAL_LOG
sleep 1
echo "XClient is Running" >$REAL_LOG
}
boot() {
ENABLE_=$(uci_get_by_type global enable 0)
if [ "$ENABLE_" == "1" ]; then
mkdir -p /var/run /var/lock /var/log /tmp/dnsmasq.d $TMP_BIN_PATH $TMP_DNSMASQ_PATH
ulimit -n 65535
start
else
if pidof xclient >/dev/null; then
stop
fi
fi
}
stop() {
echo "Stopping Client" > $REAL_LOG
unlock
set_lock
/usr/share/xclient/iptables.sh -f
$PS -w | grep -v "grep" | grep monitor | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 &
$PS -w | grep -v "grep" | grep "$TMP_PATH" | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1 &
killall -q -9 xray-plugin
rm -f /var/lock/monitor.lock
if [ -f "/tmp/dnsmasq.d/dnsmasq-xclient.conf" ]; then
rm -rf /tmp/dnsmasq.d/dnsmasq-xclient.conf $TMP_DNSMASQ_PATH $TMP_PATH/*-xclient-*.json
/etc/init.d/dnsmasq restart >/dev/null 2>&1
fi
del_cron
unset_lock
echo "0" > /usr/share/$NAME/logstatus_check >/dev/null 2>&1
echo "" > $LOG_FILE >/dev/null 2>&1
echo "XClient Stopped Successfully" > $REAL_LOG
}
reload(){
stop >/dev/null 2>&1
sleep 1
boot >/dev/null 2>&1
}
@@ -0,0 +1,29 @@
#!/bin/sh
[ ! -f "/usr/share/ucitrack/luci-app-xclient.json" ] && {
cat > /usr/share/ucitrack/luci-app-xclient.json << EEOF
{
"config": "xclient",
"init": "xclient"
}
EEOF
}
uci -q batch <<-EOF >/dev/null
delete ucitrack.@xclient[-1]
add ucitrack xclient
set ucitrack.@xclient[-1].init=xclient
commit ucitrack
delete firewall.xclient
set firewall.xclient=include
set firewall.xclient.type=script
set firewall.xclient.path=/var/etc/xclient.include
set firewall.xclient.reload=1
commit firewall
EOF
rm -rf /etc/config/xclient-opkg /etc/xclient/*opkg >/dev/null
if [ -s "/etc/uwsgi/vassals/luci-webui.ini" ];then
limit=$(cat /etc/uwsgi/vassals/luci-webui.ini | grep -Eo "limit-as.*"|grep -Eo "[0-9]+")
[ $limit -lt 5000 ] && sed -i '/limit-as/c\limit-as = 5000' /etc/uwsgi/vassals/luci-webui.ini && \
/etc/init.d/uwsgi restart
fi
rm -rf /tmp/luci-modulecache /tmp/luci-indexcache
exit 0
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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 @@
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
<defs> <clipPath id="ldio-x3jsygn7oea-cp"> <circle cx="50" cy="50" r="35"></circle> </clipPath> </defs>
<circle cx="50" cy="50" r="35" fill="#009ef7"></circle>
<g clip-path="url(#ldio-x3jsygn7oea-cp)">
<path transform="scale(2) translate(-25 -25)" fill="#dbfb54" d="M64.4,74.4c0.3,0.3,0.2,0.3-0.3,0.3C64.1,74.3,64.2,74.5,64.4,74.4z M31.4,75.4c-0.2,0.1-0.3-0.2-0.5,0.1c0.3,0.2,0.6,0.1,0.8-0.1C31.6,75.2,31.6,75.4,31.4,75.4z M84.9,36.9c-0.3,0.2-0.8-0.1-1.1-0.5 c0.1,0.2,0.1,0.6,0.3,0.9c-0.5,0.2-0.2,0.6,0.2,0.7c0-0.1,0-0.3,0-0.4c0.2,0.1,0.4,0.2,0.5,0.3c0.2-0.2,0.2-0.3,0.4-0.5 C85.1,37.2,85,37,84.9,36.9z M85.1,40.6c0-0.6-0.1-2.1-0.6-2.4c-0.6-0.4,0.5,2.1-0.6,1.6c0,0.2-0.1,0.5,0,0.7 c-0.8-0.3-0.9,0.5-1.4,0.9c0.3,0.3,0.4,0.6,0.6,0.9c0.3-0.4,0.2-0.9-0.3-1.2c0.2,0,0.4-0.2,0.6-0.2c0.2,0,0.6,0.2,0.6,0.2 C84.2,41,84.5,40.7,85.1,40.6z M83.7,41c-0.4-0.1-0.7,0.2-0.3,0.6c0.2-0.4,0-0.1,0.3-0.2C83.8,41.2,83.6,41.1,83.7,41z M93.5,62.8 c0.1,0.5,0.3,0.9,0.7,0.8C94.1,63.3,93.8,63,93.5,62.8z M93,71.2c-0.1-0.1-2.5,1.6-2.9,2.1c0.8,0.5,2.9-1.2,3.1-2.1 C93,71.5,92.9,71.2,93,71.2z M95,69.9c-0.4,0.2-0.6,0-0.5-0.4c0,0.1-0.1,0.2-0.1,0.3c0.1-0.5,0.1-0.8-0.1-1.1 c-0.3,0.7,0.1,1.4-0.7,2c0.3,0.3,0.3,0.4-0.1,0.7c0.1,0,0.1,0.1,0.1,0.2C93.9,71.3,95,70.2,95,69.9z M82,50.6 c0.2-0.2,0.3-0.2,0.3-0.5c-0.1,0-0.3-0.1-0.4-0.1C81.9,50.2,81.9,50.4,82,50.6z M82.4,50.6c-0.3-0.4-0.2,0.1-0.1,0.5 c0.2-0.3,0.3-0.5,0.2-0.9C82.5,50.4,82.4,50.5,82.4,50.6z M82.8,49.7c-0.1,0-0.2,0-0.3,0c0,0.1,0.2,0.4,0.2,0.5c-0.1,0-0.1,0-0.2,0 c0.1,0.2,0.1,0.3,0.2,0.5c0.1-0.2,0.1-0.2,0-0.5c0.1,0,0.2,0.1,0.2,0.1C83,50.1,82.9,49.9,82.8,49.7z M81,51 c0.2-0.3,0.3-0.4,0.2-0.8c-0.1,0.5-0.4,0.8-0.6,1.2C80.8,51.3,80.9,51.1,81,51z M81.2,48.6c0.1,0.2,0.1,0.5,0.3,0.3 c0,0.4,0.3,0.6,0.6,0.6c0,0,0-0.1,0.1-0.1c0.1,0.2,0.2,0.3,0.4,0.4c0-0.7-0.6-0.5-0.8-1c-0.2-0.4,0.2-0.9,0-1.4 c-0.5-0.1-0.6,0.3-0.4,1c0,0-0.1-0.1-0.1-0.1C81.1,48.3,81.1,48.4,81.2,48.6z M83.3,52.2c0.1-0.5,0.1-1.1-0.3-1.4c0,0.1,0,0.2,0,0.3 c-0.4,0.2-0.9,0.1-0.9,0.8c0.7-0.7,0.4,0.2,1,0.5c0-0.2,0.1-0.5,0.1-0.7C83.2,52,83.3,52.1,83.3,52.2z M81.5,49.6 c0.1-0.1,0.3-0.1-0.2-0.3C81.4,49.4,81.5,49.5,81.5,49.6z M91.8,57.4c0.1,0.1,0,0.2,0.2,0.1c-0.1-0.2-0.2-0.5-0.3-0.7 C91.6,57,91.7,57.1,91.8,57.4z M90.2,57.3c0.4-0.1,1.1-0.5,0.6-0.9c-0.1,0.4-0.2,0.6-0.4,0.3c-0.1,0-0.5,0.2-0.5,0.3 C89.9,57.2,90,57.2,90.2,57.3z M90.9,56.1c0.1,0.2,0.2,0.3,0.2,0.5c0.1-0.5-0.2-0.8-0.6-0.8C90.7,55.9,90.8,56,90.9,56.1z M90.3,59 c-0.2-0.4-0.7-0.7-1-1.6c0.1,0,0.2,0,0.3,0c-0.7-0.7-1.4-1.7-2.5-1.8c-0.5-0.1-1,0.5-1.3,0.3c-0.5-0.2-0.1-1.4-1.2-0.8 c0.3,0.4,0.6,0.5,0.9,0.6c-0.2,0-0.3,0.1-0.5,0.1c0.1,0.2,0.2,0.4,0.3,0.5c0.1,0,0.2-0.2,0.2-0.2c0.6,0.5,2,1,1,2 c0.6-0.3,0.7,0.4,1.2,0.3c0.4-0.1,0.4-0.5,0.6-0.5C89.2,57.9,89.5,58.9,90.3,59z M93.2,58.5c-0.1,0.1-0.2-0.2-0.2,0.1 C93.3,58.8,93.2,58.6,93.2,58.5z M92.4,57.7c0,0-0.1-0.2-0.2-0.3C92.2,57.6,92.3,57.6,92.4,57.7z M92.9,58c0.1,0.1,0.1,0.1,0.2,0.2 c-0.2-0.2-0.3-0.3-0.5-0.4C92.7,57.8,92.8,57.9,92.9,58z M93.5,59.1C93.6,58.9,93.6,58.8,93.5,59.1z M93.5,58.7c0-0.2,0-0.4-0.2-0.6 C93.3,58.3,93.4,58.5,93.5,58.7z M81,45.9c0.1-0.5,0.2-1-0.1-1.3C80.6,45,80.6,45.5,81,45.9z M94.8,61c0,0,0.1-0.2,0-0.4 C94.7,60.8,94.8,61.1,94.8,61z M94.8,61.2c0.1,0.1,0,0.3,0.2,0.1C94.9,61.1,94.8,61.2,94.8,61.2z M29.5,76.3 c-0.4-0.8-0.5-0.2-0.7-0.2c0.2,0-0.3,0.2-0.3,0.2c-0.2,0-0.6-0.3-0.9-0.4c0.9,0.9,1.9,1.2,2.6,0.7C30,76.6,29.8,76.4,29.5,76.3z M89.6,64.4c0-0.2-0.2-0.3-0.1-0.7c-0.2,0-0.1-0.1-0.3,0c0.1-0.8-0.5-1.1-0.7-1.7c-0.1-0.5,0-1-0.2-1.6c-0.1,0.1-0.1,0.1-0.2,0.2 c0-0.5-0.1-1.1-0.2-1.6c-0.5,0.9-0.5,3.8-1.7,2.2c-0.3-0.5-0.4-0.2-0.4-0.8c0-0.3,0.4-0.5,0.4-0.7c0-0.1-1.1-0.4-1.4-0.5 c0.3,0.3-0.7,0.7-0.8,1.5c-0.5-0.2-0.7-0.4-1.1-0.2c-0.4,0.1-0.7,0.9-0.7,1.1c0,0-0.1,0.1-0.1,0.1c0-0.1,0-0.2-0.1-0.3 c-0.4,0.8-0.6,1.1-1.3,1.4c-0.6,0.3-1.1,0.4-1.6,1c0-0.1,0-0.2,0-0.3c-0.3,0.6-0.5,1.1-0.3,1.8c-0.1-0.1-0.1-0.2-0.2-0.3 c0,0.1,0,0.3,0,0.4c0-0.1-0.1-0.1-0.1-0.2c0.1,1,0.1,1.7,0,2.7c0,0.4-0.6,0.5,0,0.9c0.4,0.2,0.7-0.3,1-0.4c0.8-0.2,1.4-0.5,2.3-0.7 c1.4-0.5,1.7-0.2,2.1,1.1c0.2-0.3,0.5-0.6,0.8-0.8c-0.1,0.4-0.3,0.7-0.6,1c0.2-0.1,0.3-0.2,0.5-0.4c-0.1,0.2-0.1,0.3-0.2,0.5 c0.5,0.2,0.5,2.1,1.5,0.9c0,0.1-0.1,0.1-0.1,0.2C86.9,71.3,90.8,66.1,89.6,64.4z M85.9,71.3c-0.4-0.2-1,0.2-0.7,0.8 c0.2,0.6,0.9-0.4,1.1-0.9C86.2,71.2,86.1,71.2,85.9,71.3z M24.6,44.3c0-0.2,0.2-0.3-0.1-0.5C24.5,44,24.6,44.2,24.6,44.3z M24.2,44.9c0.1,0.1,0.1,0.2,0.1,0.4c0.1-0.2,0.1-0.3,0-0.6C24.1,44.6,24.3,44.8,24.2,44.9z M24.4,43.9c-0.2,0-0.3-0.1-0.3,0.2 C24.3,44.1,24.4,43.9,24.4,43.9z M36.2,56.9c-1-0.3-1.6-1.5-2.7-1.1c0-0.7-0.8-0.9-1.2-0.6c0-0.4-0.4-0.5-0.5-0.8 c-0.1-0.1-0.1-0.7-0.2-1c-0.3-0.7-0.6-0.8-1.2-1.1c-0.6-0.3-0.6-0.2-1.1-0.8c-0.3-0.4-0.6-0.7-1-1c0.1,0,0.2-0.1,0.3-0.1 c-0.9-0.2-1.7,0.6-2.3-0.6c-0.4,0.3-0.4,0.8-0.6,1.2c0-0.3,0-0.7,0-0.9c0.4-0.6-0.1-0.2-0.5,0.1c-0.3,0.2-0.7,0.9-1,1 c0,0-1.6-0.3-1.6-0.3c-0.8-0.7,0.1-1.4-0.1-2.2c-0.2-0.8-1.1-0.3-1.6-0.5c0.2-0.3,0.3-0.7,0.3-1.1c0.1,0,0.1,0,0.1,0.1 c0.2-1.2,0.5-1.7-0.7-0.8c-0.6,0.5-1.3,1.5-1.8,0c-0.3-0.8,0-2.4,0.4-3.1c0.7-1.1,1.6-0.5,2.3-0.6c-0.1-0.7,1.4-0.4,1.6-0.3 c0.2,0.3,0.2,2.8,0.8,1.6c0.5-1-0.4-1.7,0.5-2.7c0.6-0.7,2-1.1,1.3-2.2c0.1,0,0.1,0,0.2,0.1c0-0.2,0-0.3,0.1-0.4 c0,0.4,0,0.4-0.1,0.8c0.3-0.4,0.3-0.4,0.3-0.9c0.1,0.1,0.1,0.1,0.1,0.2c0.2-0.3,0.4-0.5,0.4-0.7c0,Line truncated
<animateTransform attributeName="transform" type="translate" repeatCount="indefinite" dur="2s" keyTimes="0;1" values="-100 0;0 0"></animateTransform>
</path>
<path transform="scale(2)" fill="#dbfb54" d="M64.4,74.4c0.3,0.3,0.2,0.3-0.3,0.3C64.1,74.3,64.2,74.5,64.4,74.4z M31.4,75.4c-0.2,0.1-0.3-0.2-0.5,0.1c0.3,0.2,0.6,0.1,0.8-0.1C31.6,75.2,31.6,75.4,31.4,75.4z M84.9,36.9c-0.3,0.2-0.8-0.1-1.1-0.5 c0.1,0.2,0.1,0.6,0.3,0.9c-0.5,0.2-0.2,0.6,0.2,0.7c0-0.1,0-0.3,0-0.4c0.2,0.1,0.4,0.2,0.5,0.3c0.2-0.2,0.2-0.3,0.4-0.5 C85.1,37.2,85,37,84.9,36.9z M85.1,40.6c0-0.6-0.1-2.1-0.6-2.4c-0.6-0.4,0.5,2.1-0.6,1.6c0,0.2-0.1,0.5,0,0.7 c-0.8-0.3-0.9,0.5-1.4,0.9c0.3,0.3,0.4,0.6,0.6,0.9c0.3-0.4,0.2-0.9-0.3-1.2c0.2,0,0.4-0.2,0.6-0.2c0.2,0,0.6,0.2,0.6,0.2 C84.2,41,84.5,40.7,85.1,40.6z M83.7,41c-0.4-0.1-0.7,0.2-0.3,0.6c0.2-0.4,0-0.1,0.3-0.2C83.8,41.2,83.6,41.1,83.7,41z M93.5,62.8 c0.1,0.5,0.3,0.9,0.7,0.8C94.1,63.3,93.8,63,93.5,62.8z M93,71.2c-0.1-0.1-2.5,1.6-2.9,2.1c0.8,0.5,2.9-1.2,3.1-2.1 C93,71.5,92.9,71.2,93,71.2z M95,69.9c-0.4,0.2-0.6,0-0.5-0.4c0,0.1-0.1,0.2-0.1,0.3c0.1-0.5,0.1-0.8-0.1-1.1 c-0.3,0.7,0.1,1.4-0.7,2c0.3,0.3,0.3,0.4-0.1,0.7c0.1,0,0.1,0.1,0.1,0.2C93.9,71.3,95,70.2,95,69.9z M82,50.6 c0.2-0.2,0.3-0.2,0.3-0.5c-0.1,0-0.3-0.1-0.4-0.1C81.9,50.2,81.9,50.4,82,50.6z M82.4,50.6c-0.3-0.4-0.2,0.1-0.1,0.5 c0.2-0.3,0.3-0.5,0.2-0.9C82.5,50.4,82.4,50.5,82.4,50.6z M82.8,49.7c-0.1,0-0.2,0-0.3,0c0,0.1,0.2,0.4,0.2,0.5c-0.1,0-0.1,0-0.2,0 c0.1,0.2,0.1,0.3,0.2,0.5c0.1-0.2,0.1-0.2,0-0.5c0.1,0,0.2,0.1,0.2,0.1C83,50.1,82.9,49.9,82.8,49.7z M81,51 c0.2-0.3,0.3-0.4,0.2-0.8c-0.1,0.5-0.4,0.8-0.6,1.2C80.8,51.3,80.9,51.1,81,51z M81.2,48.6c0.1,0.2,0.1,0.5,0.3,0.3 c0,0.4,0.3,0.6,0.6,0.6c0,0,0-0.1,0.1-0.1c0.1,0.2,0.2,0.3,0.4,0.4c0-0.7-0.6-0.5-0.8-1c-0.2-0.4,0.2-0.9,0-1.4 c-0.5-0.1-0.6,0.3-0.4,1c0,0-0.1-0.1-0.1-0.1C81.1,48.3,81.1,48.4,81.2,48.6z M83.3,52.2c0.1-0.5,0.1-1.1-0.3-1.4c0,0.1,0,0.2,0,0.3 c-0.4,0.2-0.9,0.1-0.9,0.8c0.7-0.7,0.4,0.2,1,0.5c0-0.2,0.1-0.5,0.1-0.7C83.2,52,83.3,52.1,83.3,52.2z M81.5,49.6 c0.1-0.1,0.3-0.1-0.2-0.3C81.4,49.4,81.5,49.5,81.5,49.6z M91.8,57.4c0.1,0.1,0,0.2,0.2,0.1c-0.1-0.2-0.2-0.5-0.3-0.7 C91.6,57,91.7,57.1,91.8,57.4z M90.2,57.3c0.4-0.1,1.1-0.5,0.6-0.9c-0.1,0.4-0.2,0.6-0.4,0.3c-0.1,0-0.5,0.2-0.5,0.3 C89.9,57.2,90,57.2,90.2,57.3z M90.9,56.1c0.1,0.2,0.2,0.3,0.2,0.5c0.1-0.5-0.2-0.8-0.6-0.8C90.7,55.9,90.8,56,90.9,56.1z M90.3,59 c-0.2-0.4-0.7-0.7-1-1.6c0.1,0,0.2,0,0.3,0c-0.7-0.7-1.4-1.7-2.5-1.8c-0.5-0.1-1,0.5-1.3,0.3c-0.5-0.2-0.1-1.4-1.2-0.8 c0.3,0.4,0.6,0.5,0.9,0.6c-0.2,0-0.3,0.1-0.5,0.1c0.1,0.2,0.2,0.4,0.3,0.5c0.1,0,0.2-0.2,0.2-0.2c0.6,0.5,2,1,1,2 c0.6-0.3,0.7,0.4,1.2,0.3c0.4-0.1,0.4-0.5,0.6-0.5C89.2,57.9,89.5,58.9,90.3,59z M93.2,58.5c-0.1,0.1-0.2-0.2-0.2,0.1 C93.3,58.8,93.2,58.6,93.2,58.5z M92.4,57.7c0,0-0.1-0.2-0.2-0.3C92.2,57.6,92.3,57.6,92.4,57.7z M92.9,58c0.1,0.1,0.1,0.1,0.2,0.2 c-0.2-0.2-0.3-0.3-0.5-0.4C92.7,57.8,92.8,57.9,92.9,58z M93.5,59.1C93.6,58.9,93.6,58.8,93.5,59.1z M93.5,58.7c0-0.2,0-0.4-0.2-0.6 C93.3,58.3,93.4,58.5,93.5,58.7z M81,45.9c0.1-0.5,0.2-1-0.1-1.3C80.6,45,80.6,45.5,81,45.9z M94.8,61c0,0,0.1-0.2,0-0.4 C94.7,60.8,94.8,61.1,94.8,61z M94.8,61.2c0.1,0.1,0,0.3,0.2,0.1C94.9,61.1,94.8,61.2,94.8,61.2z M29.5,76.3 c-0.4-0.8-0.5-0.2-0.7-0.2c0.2,0-0.3,0.2-0.3,0.2c-0.2,0-0.6-0.3-0.9-0.4c0.9,0.9,1.9,1.2,2.6,0.7C30,76.6,29.8,76.4,29.5,76.3z M89.6,64.4c0-0.2-0.2-0.3-0.1-0.7c-0.2,0-0.1-0.1-0.3,0c0.1-0.8-0.5-1.1-0.7-1.7c-0.1-0.5,0-1-0.2-1.6c-0.1,0.1-0.1,0.1-0.2,0.2 c0-0.5-0.1-1.1-0.2-1.6c-0.5,0.9-0.5,3.8-1.7,2.2c-0.3-0.5-0.4-0.2-0.4-0.8c0-0.3,0.4-0.5,0.4-0.7c0-0.1-1.1-0.4-1.4-0.5 c0.3,0.3-0.7,0.7-0.8,1.5c-0.5-0.2-0.7-0.4-1.1-0.2c-0.4,0.1-0.7,0.9-0.7,1.1c0,0-0.1,0.1-0.1,0.1c0-0.1,0-0.2-0.1-0.3 c-0.4,0.8-0.6,1.1-1.3,1.4c-0.6,0.3-1.1,0.4-1.6,1c0-0.1,0-0.2,0-0.3c-0.3,0.6-0.5,1.1-0.3,1.8c-0.1-0.1-0.1-0.2-0.2-0.3 c0,0.1,0,0.3,0,0.4c0-0.1-0.1-0.1-0.1-0.2c0.1,1,0.1,1.7,0,2.7c0,0.4-0.6,0.5,0,0.9c0.4,0.2,0.7-0.3,1-0.4c0.8-0.2,1.4-0.5,2.3-0.7 c1.4-0.5,1.7-0.2,2.1,1.1c0.2-0.3,0.5-0.6,0.8-0.8c-0.1,0.4-0.3,0.7-0.6,1c0.2-0.1,0.3-0.2,0.5-0.4c-0.1,0.2-0.1,0.3-0.2,0.5 c0.5,0.2,0.5,2.1,1.5,0.9c0,0.1-0.1,0.1-0.1,0.2C86.9,71.3,90.8,66.1,89.6,64.4z M85.9,71.3c-0.4-0.2-1,0.2-0.7,0.8 c0.2,0.6,0.9-0.4,1.1-0.9C86.2,71.2,86.1,71.2,85.9,71.3z M24.6,44.3c0-0.2,0.2-0.3-0.1-0.5C24.5,44,24.6,44.2,24.6,44.3z M24.2,44.9c0.1,0.1,0.1,0.2,0.1,0.4c0.1-0.2,0.1-0.3,0-0.6C24.1,44.6,24.3,44.8,24.2,44.9z M24.4,43.9c-0.2,0-0.3-0.1-0.3,0.2 C24.3,44.1,24.4,43.9,24.4,43.9z M36.2,56.9c-1-0.3-1.6-1.5-2.7-1.1c0-0.7-0.8-0.9-1.2-0.6c0-0.4-0.4-0.5-0.5-0.8 c-0.1-0.1-0.1-0.7-0.2-1c-0.3-0.7-0.6-0.8-1.2-1.1c-0.6-0.3-0.6-0.2-1.1-0.8c-0.3-0.4-0.6-0.7-1-1c0.1,0,0.2-0.1,0.3-0.1 c-0.9-0.2-1.7,0.6-2.3-0.6c-0.4,0.3-0.4,0.8-0.6,1.2c0-0.3,0-0.7,0-0.9c0.4-0.6-0.1-0.2-0.5,0.1c-0.3,0.2-0.7,0.9-1,1 c0,0-1.6-0.3-1.6-0.3c-0.8-0.7,0.1-1.4-0.1-2.2c-0.2-0.8-1.1-0.3-1.6-0.5c0.2-0.3,0.3-0.7,0.3-1.1c0.1,0,0.1,0,0.1,0.1 c0.2-1.2,0.5-1.7-0.7-0.8c-0.6,0.5-1.3,1.5-1.8,0c-0.3-0.8,0-2.4,0.4-3.1c0.7-1.1,1.6-0.5,2.3-0.6c-0.1-0.7,1.4-0.4,1.6-0.3 c0.2,0.3,0.2,2.8,0.8,1.6c0.5-1-0.4-1.7,0.5-2.7c0.6-0.7,2-1.1,1.3-2.2c0.1,0,0.1,0,0.2,0.1c0-0.2,0-0.3,0.1-0.4 c0,0.4,0,0.4-0.1,0.8c0.3-0.4,0.3-0.4,0.3-0.9c0.1,0.1,0.1,0.1,0.1,0.2c0.2-0.3,0.4-0.5,0.4-0.7c0,0,0.4-0.1,0.6-0.1 cLine truncated
<animateTransform attributeName="transform" type="translate" repeatCount="indefinite" dur="2s" keyTimes="0;1" values="0 0;100 0"></animateTransform>
</path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB