parent
863195425f
commit
91e1e5ff06
153
luci-app-passwall2/Makefile
Normal file
153
luci-app-passwall2/Makefile
Normal file
@ -0,0 +1,153 @@
|
||||
# Copyright (C) 2022-2023 xiaorouji
|
||||
#
|
||||
# This is free software, licensed under the GNU General Public License v3.
|
||||
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=luci-app-passwall2
|
||||
PKG_VERSION:=1.16-7
|
||||
PKG_RELEASE:=
|
||||
|
||||
PKG_CONFIG_DEPENDS:= \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_Nftables_Transparent_Proxy \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Brook \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Haproxy \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Hysteria \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_IPv6_Nat \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Client \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Server \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Client \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Server \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Simple_Obfs \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_V2ray \
|
||||
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Plugin
|
||||
|
||||
LUCI_TITLE:=LuCI support for PassWall 2
|
||||
LUCI_PKGARCH:=all
|
||||
LUCI_DEPENDS:=+coreutils +coreutils-base64 +coreutils-nohup +curl \
|
||||
+ip-full +libuci-lua +lua +luci-compat +luci-lib-jsonc +resolveip +tcping \
|
||||
+xray-core +v2ray-geoip +v2ray-geosite \
|
||||
+unzip \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Brook:brook \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Haproxy:haproxy \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Hysteria:hysteria \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_IPv6_Nat:ip6tables-mod-nat \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy:naiveproxy \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Client:shadowsocks-libev-ss-local \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Client:shadowsocks-libev-ss-redir \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Server:shadowsocks-libev-ss-server \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Client:shadowsocks-rust-sslocal \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Server:shadowsocks-rust-ssserver \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client:shadowsocksr-libev-ssr-local \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client:shadowsocksr-libev-ssr-redir \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server:shadowsocksr-libev-ssr-server \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_Simple_Obfs:simple-obfs \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_V2ray:v2ray-core \
|
||||
+PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Plugin:v2ray-plugin
|
||||
|
||||
define Package/$(PKG_NAME)/config
|
||||
menu "Configuration"
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_Iptables_Transparent_Proxy
|
||||
bool "Iptables Transparent Proxy"
|
||||
select PACKAGE_dnsmasq-full
|
||||
select PACKAGE_dnsmasq_full_ipset
|
||||
select PACKAGE_ipset
|
||||
select PACKAGE_iptables
|
||||
select PACKAGE_iptables-nft
|
||||
select PACKAGE_iptables-zz-legacy
|
||||
select PACKAGE_iptables-mod-conntrack-extra
|
||||
select PACKAGE_iptables-mod-iprange
|
||||
select PACKAGE_iptables-mod-socket
|
||||
select PACKAGE_iptables-mod-tproxy
|
||||
select PACKAGE_kmod-ipt-nat
|
||||
depends on PACKAGE_$(PKG_NAME)
|
||||
default y if ! PACKAGE_firewall4
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_Nftables_Transparent_Proxy
|
||||
bool "Nftables Transparent Proxy"
|
||||
select PACKAGE_dnsmasq-full
|
||||
select PACKAGE_dnsmasq_full_nftset
|
||||
select PACKAGE_nftables
|
||||
select PACKAGE_kmod-nft-socket
|
||||
select PACKAGE_kmod-nft-tproxy
|
||||
select PACKAGE_kmod-nft-nat
|
||||
depends on PACKAGE_$(PKG_NAME)
|
||||
default y if PACKAGE_firewall4
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Brook
|
||||
bool "Include Brook"
|
||||
default n
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Haproxy
|
||||
bool "Include Haproxy"
|
||||
default y if aarch64||arm||i386||x86_64
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Hysteria
|
||||
bool "Include Hysteria"
|
||||
default n
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_IPv6_Nat
|
||||
depends on PACKAGE_ip6tables
|
||||
bool "Include IPv6 Nat"
|
||||
default n
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy
|
||||
bool "Include NaiveProxy"
|
||||
depends on !(arc||(arm&&TARGET_gemini)||armeb||mips||mips64||powerpc)
|
||||
default n
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Client
|
||||
bool "Include Shadowsocks Libev Client"
|
||||
default y
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Server
|
||||
bool "Include Shadowsocks Libev Server"
|
||||
default n
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Client
|
||||
bool "Include Shadowsocks Rust Client"
|
||||
depends on aarch64||arm||i386||mips||mipsel||x86_64
|
||||
default y if aarch64
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Server
|
||||
bool "Include Shadowsocks Rust Server"
|
||||
depends on aarch64||arm||i386||mips||mipsel||x86_64
|
||||
default n
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Client
|
||||
bool "Include ShadowsocksR Libev Client"
|
||||
default y
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_ShadowsocksR_Libev_Server
|
||||
bool "Include ShadowsocksR Libev Server"
|
||||
default n
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_Simple_Obfs
|
||||
bool "Include Simple-Obfs (Shadowsocks Plugin)"
|
||||
default y
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_V2ray
|
||||
bool "Include V2ray"
|
||||
default n
|
||||
|
||||
config PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Plugin
|
||||
bool "Include V2ray-Plugin (Shadowsocks Plugin)"
|
||||
default y if aarch64||arm||i386||x86_64
|
||||
|
||||
endmenu
|
||||
endef
|
||||
|
||||
define Package/$(PKG_NAME)/conffiles
|
||||
/etc/config/passwall2
|
||||
/etc/config/passwall2_server
|
||||
/usr/share/passwall2/domains_excluded
|
||||
endef
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildroot signature
|
399
luci-app-passwall2/luasrc/controller/passwall2.lua
Normal file
399
luci-app-passwall2/luasrc/controller/passwall2.lua
Normal file
@ -0,0 +1,399 @@
|
||||
-- Copyright (C) 2022-2023 xiaorouji
|
||||
|
||||
module("luci.controller.passwall2", package.seeall)
|
||||
local api = require "luci.passwall2.api"
|
||||
local appname = api.appname
|
||||
local ucic = luci.model.uci.cursor()
|
||||
local http = require "luci.http"
|
||||
local util = require "luci.util"
|
||||
local i18n = require "luci.i18n"
|
||||
|
||||
function index()
|
||||
appname = require "luci.passwall2.api".appname
|
||||
entry({"admin", "services", appname}).dependent = true
|
||||
entry({"admin", "services", appname, "reset_config"}, call("reset_config")).leaf = true
|
||||
entry({"admin", "services", appname, "show"}, call("show_menu")).leaf = true
|
||||
entry({"admin", "services", appname, "hide"}, call("hide_menu")).leaf = true
|
||||
if not nixio.fs.access("/etc/config/passwall2") then return end
|
||||
if nixio.fs.access("/etc/config/passwall2_show") then
|
||||
e = entry({"admin", "services", appname}, alias("admin", "services", appname, "settings"), _("PassWall 2"), 0)
|
||||
e.dependent = true
|
||||
e.acl_depends = { "luci-app-passwall2" }
|
||||
end
|
||||
--[[ Client ]]
|
||||
entry({"admin", "services", appname, "settings"}, cbi(appname .. "/client/global"), _("Basic Settings"), 1).dependent = true
|
||||
entry({"admin", "services", appname, "node_list"}, cbi(appname .. "/client/node_list"), _("Node List"), 2).dependent = true
|
||||
entry({"admin", "services", appname, "node_subscribe"}, cbi(appname .. "/client/node_subscribe"), _("Node Subscribe"), 3).dependent = true
|
||||
entry({"admin", "services", appname, "auto_switch"}, cbi(appname .. "/client/auto_switch"), _("Auto Switch"), 4).leaf = true
|
||||
entry({"admin", "services", appname, "other"}, cbi(appname .. "/client/other", {autoapply = true}), _("Other Settings"), 92).leaf = true
|
||||
if nixio.fs.access("/usr/sbin/haproxy") then
|
||||
entry({"admin", "services", appname, "haproxy"}, cbi(appname .. "/client/haproxy"), _("Load Balancing"), 93).leaf = true
|
||||
end
|
||||
entry({"admin", "services", appname, "app_update"}, cbi(appname .. "/client/app_update"), _("App Update"), 95).leaf = true
|
||||
entry({"admin", "services", appname, "rule"}, cbi(appname .. "/client/rule"), _("Rule Manage"), 96).leaf = true
|
||||
entry({"admin", "services", appname, "node_subscribe_config"}, cbi(appname .. "/client/node_subscribe_config")).leaf = true
|
||||
entry({"admin", "services", appname, "node_config"}, cbi(appname .. "/client/node_config")).leaf = true
|
||||
entry({"admin", "services", appname, "shunt_rules"}, cbi(appname .. "/client/shunt_rules")).leaf = true
|
||||
entry({"admin", "services", appname, "acl"}, cbi(appname .. "/client/acl"), _("Access control"), 98).leaf = true
|
||||
entry({"admin", "services", appname, "acl_config"}, cbi(appname .. "/client/acl_config")).leaf = true
|
||||
entry({"admin", "services", appname, "log"}, form(appname .. "/client/log"), _("Watch Logs"), 999).leaf = true
|
||||
|
||||
--[[ Server ]]
|
||||
entry({"admin", "services", appname, "server"}, cbi(appname .. "/server/index"), _("Server-Side"), 99).leaf = true
|
||||
entry({"admin", "services", appname, "server_user"}, cbi(appname .. "/server/user")).leaf = true
|
||||
|
||||
--[[ API ]]
|
||||
entry({"admin", "services", appname, "server_user_status"}, call("server_user_status")).leaf = true
|
||||
entry({"admin", "services", appname, "server_user_log"}, call("server_user_log")).leaf = true
|
||||
entry({"admin", "services", appname, "server_get_log"}, call("server_get_log")).leaf = true
|
||||
entry({"admin", "services", appname, "server_clear_log"}, call("server_clear_log")).leaf = true
|
||||
entry({"admin", "services", appname, "link_add_node"}, call("link_add_node")).leaf = true
|
||||
entry({"admin", "services", appname, "autoswitch_add_node"}, call("autoswitch_add_node")).leaf = true
|
||||
entry({"admin", "services", appname, "autoswitch_remove_node"}, call("autoswitch_remove_node")).leaf = true
|
||||
entry({"admin", "services", appname, "get_now_use_node"}, call("get_now_use_node")).leaf = true
|
||||
entry({"admin", "services", appname, "get_redir_log"}, call("get_redir_log")).leaf = true
|
||||
entry({"admin", "services", appname, "get_log"}, call("get_log")).leaf = true
|
||||
entry({"admin", "services", appname, "clear_log"}, call("clear_log")).leaf = true
|
||||
entry({"admin", "services", appname, "status"}, call("status")).leaf = true
|
||||
entry({"admin", "services", appname, "haproxy_status"}, call("haproxy_status")).leaf = true
|
||||
entry({"admin", "services", appname, "socks_status"}, call("socks_status")).leaf = true
|
||||
entry({"admin", "services", appname, "connect_status"}, call("connect_status")).leaf = true
|
||||
entry({"admin", "services", appname, "ping_node"}, call("ping_node")).leaf = true
|
||||
entry({"admin", "services", appname, "urltest_node"}, call("urltest_node")).leaf = true
|
||||
entry({"admin", "services", appname, "set_node"}, call("set_node")).leaf = true
|
||||
entry({"admin", "services", appname, "copy_node"}, call("copy_node")).leaf = true
|
||||
entry({"admin", "services", appname, "clear_all_nodes"}, call("clear_all_nodes")).leaf = true
|
||||
entry({"admin", "services", appname, "delete_select_nodes"}, call("delete_select_nodes")).leaf = true
|
||||
entry({"admin", "services", appname, "update_rules"}, call("update_rules")).leaf = true
|
||||
|
||||
--[[Components update]]
|
||||
local coms = require "luci.passwall2.com"
|
||||
local com
|
||||
for com, _ in pairs(coms) do
|
||||
entry({"admin", "services", appname, "check_" .. com}, call("com_check", com)).leaf = true
|
||||
entry({"admin", "services", appname, "update_" .. com}, call("com_update", com)).leaf = true
|
||||
end
|
||||
end
|
||||
|
||||
local function http_write_json(content)
|
||||
http.prepare_content("application/json")
|
||||
http.write_json(content or {code = 1})
|
||||
end
|
||||
|
||||
function reset_config()
|
||||
luci.sys.call('/etc/init.d/passwall2 stop')
|
||||
luci.sys.call('[ -f "/usr/share/passwall2/0_default_config" ] && cp -f /usr/share/passwall2/0_default_config /etc/config/passwall2')
|
||||
luci.http.redirect(api.url())
|
||||
end
|
||||
|
||||
function show_menu()
|
||||
luci.sys.call("touch /etc/config/passwall2_show")
|
||||
luci.sys.call("rm -rf /tmp/luci-*")
|
||||
luci.sys.call("/etc/init.d/rpcd restart >/dev/null")
|
||||
luci.http.redirect(api.url())
|
||||
end
|
||||
|
||||
function hide_menu()
|
||||
luci.sys.call("rm -rf /etc/config/passwall2_show")
|
||||
luci.sys.call("rm -rf /tmp/luci-*")
|
||||
luci.sys.call("/etc/init.d/rpcd restart >/dev/null")
|
||||
luci.http.redirect(luci.dispatcher.build_url("admin", "status", "overview"))
|
||||
end
|
||||
|
||||
function link_add_node()
|
||||
local lfile = "/tmp/links.conf"
|
||||
local link = luci.http.formvalue("link")
|
||||
luci.sys.call('echo \'' .. link .. '\' > ' .. lfile)
|
||||
luci.sys.call("lua /usr/share/passwall2/subscribe.lua add log")
|
||||
end
|
||||
|
||||
function autoswitch_add_node()
|
||||
local key = luci.http.formvalue("key")
|
||||
if key and key ~= "" then
|
||||
local new_list = ucic:get(appname, "@auto_switch[0]", "node") or {}
|
||||
for i = #new_list, 1, -1 do
|
||||
if (ucic:get(appname, new_list[i], "remarks") or ""):find(key) then
|
||||
table.remove(new_list, i)
|
||||
end
|
||||
end
|
||||
for k, e in ipairs(api.get_valid_nodes()) do
|
||||
if e.node_type == "normal" and e["remark"]:find(key) then
|
||||
table.insert(new_list, e.id)
|
||||
end
|
||||
end
|
||||
ucic:set_list(appname, "@auto_switch[0]", "node", new_list)
|
||||
ucic:commit(appname)
|
||||
end
|
||||
luci.http.redirect(api.url("auto_switch"))
|
||||
end
|
||||
|
||||
function autoswitch_remove_node()
|
||||
local key = luci.http.formvalue("key")
|
||||
if key and key ~= "" then
|
||||
local new_list = ucic:get(appname, "@auto_switch[0]", "node") or {}
|
||||
for i = #new_list, 1, -1 do
|
||||
if (ucic:get(appname, new_list[i], "remarks") or ""):find(key) then
|
||||
table.remove(new_list, i)
|
||||
end
|
||||
end
|
||||
ucic:set_list(appname, "@auto_switch[0]", "node", new_list)
|
||||
ucic:commit(appname)
|
||||
end
|
||||
luci.http.redirect(api.url("auto_switch"))
|
||||
end
|
||||
|
||||
function get_now_use_node()
|
||||
local e = {}
|
||||
local data, code, msg = nixio.fs.readfile("/tmp/etc/passwall2/id/global")
|
||||
if data then
|
||||
e["global"] = util.trim(data)
|
||||
end
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(e)
|
||||
end
|
||||
|
||||
function get_redir_log()
|
||||
local id = luci.http.formvalue("id")
|
||||
if nixio.fs.access("/tmp/etc/passwall2/" .. id .. ".log") then
|
||||
local content = luci.sys.exec("cat /tmp/etc/passwall2/" .. id .. ".log")
|
||||
content = content:gsub("\n", "<br />")
|
||||
luci.http.write(content)
|
||||
else
|
||||
luci.http.write(string.format("<script>alert('%s');window.close();</script>", i18n.translate("Not enabled log")))
|
||||
end
|
||||
end
|
||||
|
||||
function get_log()
|
||||
-- luci.sys.exec("[ -f /tmp/log/passwall2.log ] && sed '1!G;h;$!d' /tmp/log/passwall2.log > /tmp/log/passwall2_show.log")
|
||||
luci.http.write(luci.sys.exec("[ -f '/tmp/log/passwall2.log' ] && cat /tmp/log/passwall2.log"))
|
||||
end
|
||||
|
||||
function clear_log()
|
||||
luci.sys.call("echo '' > /tmp/log/passwall2.log")
|
||||
end
|
||||
|
||||
function status()
|
||||
local e = {}
|
||||
e["global_status"] = luci.sys.call(string.format("top -bn1 | grep -v -E 'grep|acl/|acl_' | grep '%s/bin/' | grep -i 'global\\.json' >/dev/null", appname)) == 0
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(e)
|
||||
end
|
||||
|
||||
function haproxy_status()
|
||||
local e = luci.sys.call(string.format("top -bn1 | grep -v grep | grep '%s/bin/' | grep haproxy >/dev/null", appname)) == 0
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(e)
|
||||
end
|
||||
|
||||
function socks_status()
|
||||
local e = {}
|
||||
local index = luci.http.formvalue("index")
|
||||
local id = luci.http.formvalue("id")
|
||||
e.index = index
|
||||
e.socks_status = luci.sys.call(string.format("top -bn1 | grep -v -E 'grep|acl/|acl_' | grep '%s/bin/' | grep '%s' | grep 'SOCKS_' > /dev/null", appname, id)) == 0
|
||||
local use_http = ucic:get(appname, id, "http_port") or 0
|
||||
e.use_http = 0
|
||||
if tonumber(use_http) > 0 then
|
||||
e.use_http = 1
|
||||
e.http_status = luci.sys.call(string.format("top -bn1 | grep -v -E 'grep|acl/|acl_' | grep '%s/bin/' | grep '%s' | grep -E 'HTTP_|HTTP2SOCKS' > /dev/null", appname, id)) == 0
|
||||
end
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(e)
|
||||
end
|
||||
|
||||
function connect_status()
|
||||
local e = {}
|
||||
e.use_time = ""
|
||||
local url = luci.http.formvalue("url")
|
||||
local result = luci.sys.exec('curl --connect-timeout 3 -o /dev/null -I -sk -w "%{http_code}:%{time_starttransfer}" ' .. url)
|
||||
local code = tonumber(luci.sys.exec("echo -n '" .. result .. "' | awk -F ':' '{print $1}'") or "0")
|
||||
if code ~= 0 then
|
||||
local use_time = luci.sys.exec("echo -n '" .. result .. "' | awk -F ':' '{print $2}'")
|
||||
if use_time:find("%.") then
|
||||
e.use_time = string.format("%.2f", use_time * 1000)
|
||||
else
|
||||
e.use_time = string.format("%.2f", use_time / 1000)
|
||||
end
|
||||
e.ping_type = "curl"
|
||||
end
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(e)
|
||||
end
|
||||
|
||||
function ping_node()
|
||||
local index = luci.http.formvalue("index")
|
||||
local address = luci.http.formvalue("address")
|
||||
local port = luci.http.formvalue("port")
|
||||
local e = {}
|
||||
e.index = index
|
||||
local nodes_ping = ucic:get(appname, "@global_other[0]", "nodes_ping") or ""
|
||||
if nodes_ping:find("tcping") and luci.sys.exec("echo -n $(command -v tcping)") ~= "" then
|
||||
if api.is_ipv6(address) then
|
||||
address = api.get_ipv6_only(address)
|
||||
end
|
||||
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, address))
|
||||
end
|
||||
if e.ping == nil or tonumber(e.ping) == 0 then
|
||||
e.ping = luci.sys.exec("echo -n $(ping -c 1 -W 1 %q 2>&1 | grep -o 'time=[0-9]*' | awk -F '=' '{print $2}') 2>/dev/null" % address)
|
||||
end
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(e)
|
||||
end
|
||||
|
||||
function urltest_node()
|
||||
local index = luci.http.formvalue("index")
|
||||
local id = luci.http.formvalue("id")
|
||||
local e = {}
|
||||
e.index = index
|
||||
local result = luci.sys.exec(string.format("/usr/share/passwall2/test.sh url_test_node %s %s", id, "urltest_node"))
|
||||
local code = tonumber(luci.sys.exec("echo -n '" .. result .. "' | awk -F ':' '{print $1}'") or "0")
|
||||
if code ~= 0 then
|
||||
local use_time = luci.sys.exec("echo -n '" .. result .. "' | awk -F ':' '{print $2}'")
|
||||
if use_time:find("%.") then
|
||||
e.use_time = string.format("%.2f", use_time * 1000)
|
||||
else
|
||||
e.use_time = string.format("%.2f", use_time / 1000)
|
||||
end
|
||||
end
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write_json(e)
|
||||
end
|
||||
|
||||
function set_node()
|
||||
local type = luci.http.formvalue("type")
|
||||
local config = luci.http.formvalue("config")
|
||||
local section = luci.http.formvalue("section")
|
||||
ucic:set(appname, type, config, section)
|
||||
ucic:commit(appname)
|
||||
luci.sys.call("/etc/init.d/passwall2 restart > /dev/null 2>&1 &")
|
||||
luci.http.redirect(api.url("log"))
|
||||
end
|
||||
|
||||
function copy_node()
|
||||
local section = luci.http.formvalue("section")
|
||||
local uuid = api.gen_short_uuid()
|
||||
ucic:section(appname, "nodes", uuid)
|
||||
for k, v in pairs(ucic:get_all(appname, section)) do
|
||||
local filter = k:find("%.")
|
||||
if filter and filter == 1 then
|
||||
else
|
||||
xpcall(function()
|
||||
ucic:set(appname, uuid, k, v)
|
||||
end,
|
||||
function(e)
|
||||
end)
|
||||
end
|
||||
end
|
||||
ucic:delete(appname, uuid, "add_from")
|
||||
ucic:set(appname, uuid, "add_mode", 1)
|
||||
ucic:commit(appname)
|
||||
luci.http.redirect(api.url("node_config", uuid))
|
||||
end
|
||||
|
||||
function clear_all_nodes()
|
||||
ucic:set(appname, '@global[0]', "enabled", "0")
|
||||
ucic:set(appname, '@global[0]', "node", "nil")
|
||||
ucic:set_list(appname, "@auto_switch[0]", "node", {})
|
||||
ucic:foreach(appname, "socks", function(t)
|
||||
ucic:delete(appname, t[".name"])
|
||||
end)
|
||||
ucic:foreach(appname, "haproxy_config", function(t)
|
||||
ucic:delete(appname, t[".name"])
|
||||
end)
|
||||
ucic:foreach(appname, "acl_rule", function(t)
|
||||
ucic:set(appname, t[".name"], "node", "default")
|
||||
end)
|
||||
ucic:foreach(appname, "nodes", function(node)
|
||||
ucic:delete(appname, node['.name'])
|
||||
end)
|
||||
|
||||
ucic:commit(appname)
|
||||
luci.sys.call("/etc/init.d/" .. appname .. " stop")
|
||||
end
|
||||
|
||||
function delete_select_nodes()
|
||||
local ids = luci.http.formvalue("ids")
|
||||
local auto_switch_node_list = ucic:get(appname, "@auto_switch[0]", "node") or {}
|
||||
string.gsub(ids, '[^' .. "," .. ']+', function(w)
|
||||
for i = #auto_switch_node_list, 1, -1 do
|
||||
if w == auto_switch_node_list[i] then
|
||||
table.remove(auto_switch_node_list, i)
|
||||
end
|
||||
end
|
||||
ucic:set_list(appname, "@auto_switch[0]", "node", auto_switch_node_list)
|
||||
if (ucic:get(appname, "@global[0]", "node") or "nil") == w then
|
||||
ucic:set(appname, '@global[0]', "node", "nil")
|
||||
end
|
||||
ucic:foreach(appname, "socks", function(t)
|
||||
if t["node"] == w then
|
||||
ucic:delete(appname, t[".name"])
|
||||
end
|
||||
end)
|
||||
ucic:foreach(appname, "haproxy_config", function(t)
|
||||
if t["lbss"] == w then
|
||||
ucic:delete(appname, t[".name"])
|
||||
end
|
||||
end)
|
||||
ucic:foreach(appname, "acl_rule", function(t)
|
||||
if t["node"] == w then
|
||||
ucic:set(appname, t[".name"], "node", "default")
|
||||
end
|
||||
end)
|
||||
ucic:delete(appname, w)
|
||||
end)
|
||||
ucic:commit(appname)
|
||||
luci.sys.call("/etc/init.d/" .. appname .. " restart > /dev/null 2>&1 &")
|
||||
end
|
||||
|
||||
function update_rules()
|
||||
local update = luci.http.formvalue("update")
|
||||
luci.sys.call("lua /usr/share/passwall2/rule_update.lua log '" .. update .. "' > /dev/null 2>&1 &")
|
||||
http_write_json()
|
||||
end
|
||||
|
||||
function server_user_status()
|
||||
local e = {}
|
||||
e.index = luci.http.formvalue("index")
|
||||
e.status = luci.sys.call(string.format("top -bn1 | grep -v 'grep' | grep '%s/bin/' | grep -i '%s' >/dev/null", appname .. "_server", luci.http.formvalue("id"))) == 0
|
||||
http_write_json(e)
|
||||
end
|
||||
|
||||
function server_user_log()
|
||||
local id = luci.http.formvalue("id")
|
||||
if nixio.fs.access("/tmp/etc/passwall2_server/" .. id .. ".log") then
|
||||
local content = luci.sys.exec("cat /tmp/etc/passwall2_server/" .. id .. ".log")
|
||||
content = content:gsub("\n", "<br />")
|
||||
luci.http.write(content)
|
||||
else
|
||||
luci.http.write(string.format("<script>alert('%s');window.close();</script>", i18n.translate("Not enabled log")))
|
||||
end
|
||||
end
|
||||
|
||||
function server_get_log()
|
||||
luci.http.write(luci.sys.exec("[ -f '/tmp/log/passwall2_server.log' ] && cat /tmp/log/passwall2_server.log"))
|
||||
end
|
||||
|
||||
function server_clear_log()
|
||||
luci.sys.call("echo '' > /tmp/log/passwall2_server.log")
|
||||
end
|
||||
|
||||
function com_check(comname)
|
||||
local json = api.to_check("", comname)
|
||||
http_write_json(json)
|
||||
end
|
||||
|
||||
function com_update(comname)
|
||||
local json = nil
|
||||
local task = http.formvalue("task")
|
||||
if task == "extract" then
|
||||
json = api.to_extract(comname, http.formvalue("file"), http.formvalue("subfix"))
|
||||
elseif task == "move" then
|
||||
json = api.to_move(comname, http.formvalue("file"))
|
||||
else
|
||||
json = api.to_download(comname, http.formvalue("url"), http.formvalue("size"))
|
||||
end
|
||||
|
||||
http_write_json(json)
|
||||
end
|
||||
|
||||
|
62
luci-app-passwall2/luasrc/model/cbi/passwall2/client/acl.lua
Normal file
62
luci-app-passwall2/luasrc/model/cbi/passwall2/client/acl.lua
Normal file
@ -0,0 +1,62 @@
|
||||
local api = require "luci.passwall2.api"
|
||||
local appname = api.appname
|
||||
local sys = api.sys
|
||||
local has_chnlist = api.fs.access("/usr/share/passwall2/rules/chnlist")
|
||||
|
||||
m = Map(appname)
|
||||
|
||||
s = m:section(TypedSection, "global", translate("ACLs"), "<font color='red'>" .. translate("ACLs is a tools which used to designate specific IP proxy mode.") .. "</font>")
|
||||
s.anonymous = true
|
||||
|
||||
o = s:option(Flag, "acl_enable", translate("Main switch"))
|
||||
o.rmempty = false
|
||||
o.default = false
|
||||
|
||||
-- [[ ACLs Settings ]]--
|
||||
s = m:section(TypedSection, "acl_rule")
|
||||
s.template = "cbi/tblsection"
|
||||
s.sortable = true
|
||||
s.anonymous = true
|
||||
s.addremove = true
|
||||
s.extedit = api.url("acl_config", "%s")
|
||||
function s.create(e, t)
|
||||
t = TypedSection.create(e, t)
|
||||
luci.http.redirect(e.extedit:format(t))
|
||||
end
|
||||
|
||||
---- Enable
|
||||
o = s:option(Flag, "enabled", translate("Enable"))
|
||||
o.default = 1
|
||||
o.rmempty = false
|
||||
|
||||
---- Remarks
|
||||
o = s:option(Value, "remarks", translate("Remarks"))
|
||||
o.rmempty = true
|
||||
|
||||
local mac_t = {}
|
||||
sys.net.mac_hints(function(e, t)
|
||||
mac_t[e] = {
|
||||
ip = t,
|
||||
mac = e
|
||||
}
|
||||
end)
|
||||
|
||||
o = s:option(DummyValue, "sources", translate("Source"))
|
||||
o.rawhtml = true
|
||||
o.cfgvalue = function(t, n)
|
||||
local e = ''
|
||||
local v = Value.cfgvalue(t, n) or ''
|
||||
string.gsub(v, '[^' .. " " .. ']+', function(w)
|
||||
local a = w
|
||||
if mac_t[w] then
|
||||
a = a .. ' (' .. mac_t[w].ip .. ')'
|
||||
end
|
||||
if #e > 0 then
|
||||
e = e .. "<br />"
|
||||
end
|
||||
e = e .. a
|
||||
end)
|
||||
return e
|
||||
end
|
||||
|
||||
return m
|
@ -0,0 +1,294 @@
|
||||
local api = require "luci.passwall2.api"
|
||||
local appname = api.appname
|
||||
local sys = api.sys
|
||||
|
||||
m = Map(appname)
|
||||
|
||||
local nodes_table = {}
|
||||
for k, e in ipairs(api.get_valid_nodes()) do
|
||||
nodes_table[#nodes_table + 1] = e
|
||||
end
|
||||
|
||||
local dynamicList_write = function(self, section, value)
|
||||
local t = {}
|
||||
local t2 = {}
|
||||
if type(value) == "table" then
|
||||
local x
|
||||
for _, x in ipairs(value) do
|
||||
if x and #x > 0 then
|
||||
if not t2[x] then
|
||||
t2[x] = x
|
||||
t[#t+1] = x
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
t = { value }
|
||||
end
|
||||
t = table.concat(t, " ")
|
||||
return DynamicList.write(self, section, t)
|
||||
end
|
||||
local doh_validate = function(self, value, t)
|
||||
if value ~= "" then
|
||||
local flag = 0
|
||||
local util = require "luci.util"
|
||||
local val = util.split(value, ",")
|
||||
local url = val[1]
|
||||
val[1] = nil
|
||||
for i = 1, #val do
|
||||
local v = val[i]
|
||||
if v then
|
||||
if not datatypes.ipmask4(v) then
|
||||
flag = 1
|
||||
end
|
||||
end
|
||||
end
|
||||
if flag == 0 then
|
||||
return value
|
||||
end
|
||||
end
|
||||
return nil, translate("DoH request address") .. " " .. translate("Format must be:") .. " URL,IP"
|
||||
end
|
||||
-- [[ ACLs Settings ]]--
|
||||
s = m:section(NamedSection, arg[1], translate("ACLs"), translate("ACLs"))
|
||||
s.addremove = false
|
||||
s.dynamic = false
|
||||
|
||||
---- Enable
|
||||
o = s:option(Flag, "enabled", translate("Enable"))
|
||||
o.default = 1
|
||||
o.rmempty = false
|
||||
|
||||
---- Remarks
|
||||
o = s:option(Value, "remarks", translate("Remarks"))
|
||||
o.default = arg[1]
|
||||
o.rmempty = true
|
||||
|
||||
local mac_t = {}
|
||||
sys.net.mac_hints(function(e, t)
|
||||
mac_t[#mac_t + 1] = {
|
||||
ip = t,
|
||||
mac = e
|
||||
}
|
||||
end)
|
||||
table.sort(mac_t, function(a,b)
|
||||
if #a.ip < #b.ip then
|
||||
return true
|
||||
elseif #a.ip == #b.ip then
|
||||
if a.ip < b.ip then
|
||||
return true
|
||||
else
|
||||
return #a.ip < #b.ip
|
||||
end
|
||||
end
|
||||
return false
|
||||
end)
|
||||
|
||||
---- Source
|
||||
sources = s:option(DynamicList, "sources", translate("Source"))
|
||||
sources.description = "<ul><li>" .. translate("Example:")
|
||||
.. "</li><li>" .. translate("MAC") .. ": 00:00:00:FF:FF:FF"
|
||||
.. "</li><li>" .. translate("IP") .. ": 192.168.1.100"
|
||||
.. "</li><li>" .. translate("IP CIDR") .. ": 192.168.1.0/24"
|
||||
.. "</li><li>" .. translate("IP range") .. ": 192.168.1.100-192.168.1.200"
|
||||
.. "</li><li>" .. translate("IPSet") .. ": ipset:lanlist"
|
||||
.. "</li></ul>"
|
||||
sources.cast = "string"
|
||||
for _, key in pairs(mac_t) do
|
||||
sources:value(key.mac, "%s (%s)" % {key.mac, key.ip})
|
||||
end
|
||||
sources.cfgvalue = function(self, section)
|
||||
local value
|
||||
if self.tag_error[section] then
|
||||
value = self:formvalue(section)
|
||||
else
|
||||
value = self.map:get(section, self.option)
|
||||
if type(value) == "string" then
|
||||
local value2 = {}
|
||||
string.gsub(value, '[^' .. " " .. ']+', function(w) table.insert(value2, w) end)
|
||||
value = value2
|
||||
end
|
||||
end
|
||||
return value
|
||||
end
|
||||
sources.validate = function(self, value, t)
|
||||
local err = {}
|
||||
for _, v in ipairs(value) do
|
||||
local flag = false
|
||||
if v:find("ipset:") and v:find("ipset:") == 1 then
|
||||
local ipset = v:gsub("ipset:", "")
|
||||
if ipset and ipset ~= "" then
|
||||
flag = true
|
||||
end
|
||||
end
|
||||
|
||||
if flag == false and datatypes.macaddr(v) then
|
||||
flag = true
|
||||
end
|
||||
|
||||
if flag == false and datatypes.ip4addr(v) then
|
||||
flag = true
|
||||
end
|
||||
|
||||
if flag == false and api.iprange(v) then
|
||||
flag = true
|
||||
end
|
||||
|
||||
if flag == false then
|
||||
err[#err + 1] = v
|
||||
end
|
||||
end
|
||||
|
||||
if #err > 0 then
|
||||
self:add_error(t, "invalid", translate("Not true format, please re-enter!"))
|
||||
for _, v in ipairs(err) do
|
||||
self:add_error(t, "invalid", v)
|
||||
end
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
sources.write = dynamicList_write
|
||||
|
||||
---- TCP No Redir Ports
|
||||
o = s:option(Value, "tcp_no_redir_ports", translate("TCP No Redir Ports"))
|
||||
o.default = "default"
|
||||
o:value("disable", translate("No patterns are used"))
|
||||
o:value("default", translate("Default"))
|
||||
o:value("1:65535", translate("All"))
|
||||
|
||||
---- UDP No Redir Ports
|
||||
o = s:option(Value, "udp_no_redir_ports", translate("UDP No Redir Ports"))
|
||||
o.default = "default"
|
||||
o:value("disable", translate("No patterns are used"))
|
||||
o:value("default", translate("Default"))
|
||||
o:value("1:65535", translate("All"))
|
||||
|
||||
---- TCP Redir Ports
|
||||
o = s:option(Value, "tcp_redir_ports", translate("TCP Redir Ports"))
|
||||
o.default = "default"
|
||||
o:value("default", translate("Default"))
|
||||
o:value("1:65535", translate("All"))
|
||||
o:value("22,25,53,143,465,587,853,993,995,80,443", translate("Common Use"))
|
||||
o:value("80,443", "80,443")
|
||||
|
||||
---- UDP Redir Ports
|
||||
o = s:option(Value, "udp_redir_ports", translate("UDP Redir Ports"))
|
||||
o.default = "default"
|
||||
o:value("default", translate("Default"))
|
||||
o:value("1:65535", translate("All"))
|
||||
|
||||
node = s:option(ListValue, "node", "<a style='color: red'>" .. translate("Node") .. "</a>")
|
||||
node.default = "default"
|
||||
node:value("default", translate("Default"))
|
||||
|
||||
for k, v in pairs(nodes_table) do
|
||||
node:value(v.id, v["remark"])
|
||||
end
|
||||
|
||||
o = s:option(ListValue, "direct_dns_protocol", translate("Direct DNS Protocol"))
|
||||
o.default = "auto"
|
||||
o:value("auto", translate("Auto"))
|
||||
o:value("udp", "UDP")
|
||||
o:value("tcp", "TCP")
|
||||
o:value("doh", "DoH")
|
||||
o:depends({ node = "default", ['!reverse'] = true })
|
||||
---- DNS Forward
|
||||
o = s:option(Value, "direct_dns", translate("Direct DNS"))
|
||||
o.datatype = "or(ipaddr,ipaddrport)"
|
||||
o.default = "119.29.29.29"
|
||||
o:value("114.114.114.114", "114.114.114.114 (114DNS)")
|
||||
o:value("119.29.29.29", "119.29.29.29 (DNSPod)")
|
||||
o:value("223.5.5.5", "223.5.5.5 (AliDNS)")
|
||||
o:depends("direct_dns_protocol", "udp")
|
||||
o:depends("direct_dns_protocol", "tcp")
|
||||
|
||||
---- DoH
|
||||
o = s:option(Value, "direct_dns_doh", translate("Direct DNS DoH"))
|
||||
o.default = "https://223.5.5.5/dns-query"
|
||||
o:value("https://1.12.12.12/dns-query", "DNSPod 1")
|
||||
o:value("https://120.53.53.53/dns-query", "DNSPod 2")
|
||||
o:value("https://223.5.5.5/dns-query", "AliDNS")
|
||||
o.validate = doh_validate
|
||||
o:depends("direct_dns_protocol", "doh")
|
||||
|
||||
o = s:option(Value, "direct_dns_client_ip", translate("Direct DNS EDNS Client Subnet"))
|
||||
o.description = translate("Notify the DNS server when the DNS query is notified, the location of the client (cannot be a private IP address).") .. "<br />" ..
|
||||
translate("This feature requires the DNS server to support the Edns Client Subnet (RFC7871).")
|
||||
o.datatype = "ipaddr"
|
||||
o:depends("direct_dns_protocol", "tcp")
|
||||
o:depends("direct_dns_protocol", "doh")
|
||||
|
||||
o = s:option(ListValue, "direct_dns_query_strategy", translate("Direct Query Strategy"))
|
||||
o.default = "UseIP"
|
||||
o:value("UseIP")
|
||||
o:value("UseIPv4")
|
||||
o:value("UseIPv6")
|
||||
o:depends({ node = "default", ['!reverse'] = true })
|
||||
|
||||
o = s:option(ListValue, "remote_dns_protocol", translate("Remote DNS Protocol"))
|
||||
o:value("tcp", "TCP")
|
||||
o:value("doh", "DoH")
|
||||
o:value("udp", "UDP")
|
||||
o:depends({ node = "default", ['!reverse'] = true })
|
||||
|
||||
---- DNS Forward
|
||||
o = s:option(Value, "remote_dns", translate("Remote DNS"))
|
||||
o.datatype = "or(ipaddr,ipaddrport)"
|
||||
o.default = "1.1.1.1"
|
||||
o:value("1.1.1.1", "1.1.1.1 (CloudFlare)")
|
||||
o:value("1.1.1.2", "1.1.1.2 (CloudFlare-Security)")
|
||||
o:value("8.8.4.4", "8.8.4.4 (Google)")
|
||||
o:value("8.8.8.8", "8.8.8.8 (Google)")
|
||||
o:value("9.9.9.9", "9.9.9.9 (Quad9-Recommended)")
|
||||
o:value("208.67.220.220", "208.67.220.220 (OpenDNS)")
|
||||
o:value("208.67.222.222", "208.67.222.222 (OpenDNS)")
|
||||
o:depends("remote_dns_protocol", "tcp")
|
||||
o:depends("remote_dns_protocol", "udp")
|
||||
|
||||
---- DoH
|
||||
o = s:option(Value, "remote_dns_doh", translate("Remote DNS DoH"))
|
||||
o:value("https://1.1.1.1/dns-query", "CloudFlare")
|
||||
o:value("https://1.1.1.2/dns-query", "CloudFlare-Security")
|
||||
o:value("https://8.8.4.4/dns-query", "Google 8844")
|
||||
o:value("https://8.8.8.8/dns-query", "Google 8888")
|
||||
o:value("https://9.9.9.9/dns-query", "Quad9-Recommended")
|
||||
o:value("https://208.67.222.222/dns-query", "OpenDNS")
|
||||
o:value("https://dns.adguard.com/dns-query,176.103.130.130", "AdGuard")
|
||||
o:value("https://doh.libredns.gr/dns-query,116.202.176.26", "LibreDNS")
|
||||
o:value("https://doh.libredns.gr/ads,116.202.176.26", "LibreDNS (No Ads)")
|
||||
o.default = "https://1.1.1.1/dns-query"
|
||||
o.validate = doh_validate
|
||||
o:depends("remote_dns_protocol", "doh")
|
||||
|
||||
o = s:option(Value, "remote_dns_client_ip", translate("Remote DNS EDNS Client Subnet"))
|
||||
o.description = translate("Notify the DNS server when the DNS query is notified, the location of the client (cannot be a private IP address).") .. "<br />" ..
|
||||
translate("This feature requires the DNS server to support the Edns Client Subnet (RFC7871).")
|
||||
o.datatype = "ipaddr"
|
||||
o:depends("remote_dns_protocol", "tcp")
|
||||
o:depends("remote_dns_protocol", "doh")
|
||||
|
||||
o = s:option(Flag, "remote_fakedns", "FakeDNS", translate("Use FakeDNS work in the shunt domain that proxy."))
|
||||
o.default = "0"
|
||||
o.rmempty = false
|
||||
o:depends("remote_dns_protocol", "tcp")
|
||||
o:depends("remote_dns_protocol", "doh")
|
||||
o:depends("remote_dns_protocol", "udp")
|
||||
|
||||
o = s:option(ListValue, "remote_dns_query_strategy", translate("Remote Query Strategy"))
|
||||
o.default = "UseIPv4"
|
||||
o:value("UseIP")
|
||||
o:value("UseIPv4")
|
||||
o:value("UseIPv6")
|
||||
o:depends("remote_dns_protocol", "tcp")
|
||||
o:depends("remote_dns_protocol", "doh")
|
||||
o:depends("remote_dns_protocol", "udp")
|
||||
|
||||
hosts = s:option(TextValue, "dns_hosts", translate("Domain Override"))
|
||||
hosts.rows = 5
|
||||
hosts.wrap = "off"
|
||||
hosts:depends("remote_dns_protocol", "tcp")
|
||||
hosts:depends("remote_dns_protocol", "doh")
|
||||
hosts:depends("remote_dns_protocol", "udp")
|
||||
|
||||
return m
|
@ -0,0 +1,28 @@
|
||||
local api = require "luci.passwall2.api"
|
||||
local appname = api.appname
|
||||
|
||||
m = Map(appname)
|
||||
|
||||
-- [[ App Settings ]]--
|
||||
s = m:section(TypedSection, "global_app", translate("App Update"),
|
||||
"<font color='red'>" ..
|
||||
translate("Please confirm that your firmware supports FPU.") ..
|
||||
"</font>")
|
||||
s.anonymous = true
|
||||
s:append(Template(appname .. "/app_update/app_version"))
|
||||
|
||||
local k, v
|
||||
local com = require "luci.passwall2.com"
|
||||
for k, v in pairs(com) do
|
||||
o = s:option(Value, k:gsub("%-","_") .. "_file", translatef("%s App Path", v.name))
|
||||
o.default = v.default_path or ("/usr/bin/" .. k)
|
||||
o.rmempty = false
|
||||
end
|
||||
|
||||
o = s:option(DummyValue, "tips", " ")
|
||||
o.rawhtml = true
|
||||
o.cfgvalue = function(t, n)
|
||||
return string.format('<font color="red">%s</font>', translate("if you want to run from memory, change the path, /tmp beginning then save the application and update it manually."))
|
||||
end
|
||||
|
||||
return m
|
@ -0,0 +1,66 @@
|
||||
local api = require "luci.passwall2.api"
|
||||
local appname = api.appname
|
||||
|
||||
local nodes_table = {}
|
||||
for k, e in ipairs(api.get_valid_nodes()) do
|
||||
nodes_table[#nodes_table + 1] = e
|
||||
end
|
||||
|
||||
m = Map(appname)
|
||||
|
||||
-- [[ Auto Switch Settings ]]--
|
||||
s = m:section(TypedSection, "auto_switch")
|
||||
s.anonymous = true
|
||||
|
||||
---- Enable
|
||||
o = s:option(Flag, "enable", translate("Enable"))
|
||||
o.default = 0
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(Value, "testing_time", translate("How often to test"), translate("Units:minutes"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 1
|
||||
|
||||
o = s:option(Value, "connect_timeout", translate("Timeout seconds"), translate("Units:seconds"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 3
|
||||
|
||||
o = s:option(Value, "retry_num", translate("Timeout retry num"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 3
|
||||
|
||||
o = s:option(DynamicList, "node", translate("List of backup nodes"))
|
||||
for k, v in pairs(nodes_table) do
|
||||
if v.node_type == "normal" then
|
||||
o:value(v.id, v["remark"])
|
||||
end
|
||||
end
|
||||
function o.write(self, section, value)
|
||||
local t = {}
|
||||
local t2 = {}
|
||||
if type(value) == "table" then
|
||||
local x
|
||||
for _, x in ipairs(value) do
|
||||
if x and #x > 0 then
|
||||
if not t2[x] then
|
||||
t2[x] = x
|
||||
t[#t+1] = x
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
t = { value }
|
||||
end
|
||||
return DynamicList.write(self, section, t)
|
||||
end
|
||||
|
||||
o = s:option(Flag, "restore_switch", translate("Restore Switch"), translate("When detects main node is available, switch back to the main node."))
|
||||
|
||||
o = s:option(ListValue, "shunt_logic", translate("If the main node is shunt"))
|
||||
o:value("0", translate("Switch it"))
|
||||
o:value("1", translate("Applying to the default node"))
|
||||
o:value("2", translate("Applying to the default preproxy node"))
|
||||
|
||||
m:append(Template(appname .. "/auto_switch/footer"))
|
||||
|
||||
return m
|
425
luci-app-passwall2/luasrc/model/cbi/passwall2/client/global.lua
Normal file
425
luci-app-passwall2/luasrc/model/cbi/passwall2/client/global.lua
Normal file
@ -0,0 +1,425 @@
|
||||
local api = require "luci.passwall2.api"
|
||||
local appname = api.appname
|
||||
local uci = api.uci
|
||||
local datatypes = api.datatypes
|
||||
local has_v2ray = api.is_finded("v2ray")
|
||||
local has_xray = api.is_finded("xray")
|
||||
|
||||
m = Map(appname)
|
||||
|
||||
local nodes_table = {}
|
||||
for k, e in ipairs(api.get_valid_nodes()) do
|
||||
nodes_table[#nodes_table + 1] = e
|
||||
end
|
||||
|
||||
local doh_validate = function(self, value, t)
|
||||
if value ~= "" then
|
||||
local flag = 0
|
||||
local util = require "luci.util"
|
||||
local val = util.split(value, ",")
|
||||
local url = val[1]
|
||||
val[1] = nil
|
||||
for i = 1, #val do
|
||||
local v = val[i]
|
||||
if v then
|
||||
if not datatypes.ipmask4(v) then
|
||||
flag = 1
|
||||
end
|
||||
end
|
||||
end
|
||||
if flag == 0 then
|
||||
return value
|
||||
end
|
||||
end
|
||||
return nil, translate("DoH request address") .. " " .. translate("Format must be:") .. " URL,IP"
|
||||
end
|
||||
|
||||
m:append(Template(appname .. "/global/status"))
|
||||
|
||||
s = m:section(TypedSection, "global")
|
||||
s.anonymous = true
|
||||
s.addremove = false
|
||||
|
||||
s:tab("Main", translate("Main"))
|
||||
|
||||
-- [[ Global Settings ]]--
|
||||
o = s:taboption("Main", Flag, "enabled", translate("Main switch"))
|
||||
o.rmempty = false
|
||||
|
||||
local auto_switch_tip
|
||||
local shunt_remark
|
||||
local current_node = luci.sys.exec(string.format("[ -f '/tmp/etc/%s/id/global' ] && echo -n $(cat /tmp/etc/%s/id/global)", appname, appname))
|
||||
if current_node and current_node ~= "" and current_node ~= "nil" then
|
||||
local n = uci:get_all(appname, current_node)
|
||||
if n then
|
||||
if tonumber(m:get("@auto_switch[0]", "enable") or 0) == 1 then
|
||||
if n.protocol == "_shunt" then
|
||||
local shunt_logic = tonumber(m:get("@auto_switch[0]", "shunt_logic"))
|
||||
if shunt_logic == 1 or shunt_logic == 2 then
|
||||
if shunt_logic == 1 then
|
||||
shunt_remark = "default"
|
||||
elseif shunt_logic == 2 then
|
||||
shunt_remark = "main"
|
||||
end
|
||||
current_node = luci.sys.exec(string.format("[ -f '/tmp/etc/%s/id/global_%s' ] && echo -n $(cat /tmp/etc/%s/id/global_%s)", appname, shunt_remark, appname, shunt_remark))
|
||||
if current_node and current_node ~= "" and current_node ~= "nil" then
|
||||
n = uci:get_all(appname, current_node)
|
||||
end
|
||||
end
|
||||
end
|
||||
if n then
|
||||
local remarks = api.get_node_remarks(n)
|
||||
local url = api.url("node_config", n[".name"])
|
||||
auto_switch_tip = translatef("Current node: %s", string.format('<a href="%s">%s</a>', url, remarks)) .. "<br />"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---- Node
|
||||
node = s:taboption("Main", ListValue, "node", "<a style='color: red'>" .. translate("Node") .. "</a>")
|
||||
node:value("nil", translate("Close"))
|
||||
if not shunt_remark and auto_switch_tip then
|
||||
node.description = auto_switch_tip
|
||||
end
|
||||
|
||||
-- 分流
|
||||
if (has_v2ray or has_xray) and #nodes_table > 0 then
|
||||
local normal_list = {}
|
||||
local balancing_list = {}
|
||||
local shunt_list = {}
|
||||
for k, v in pairs(nodes_table) do
|
||||
if v.node_type == "normal" then
|
||||
normal_list[#normal_list + 1] = v
|
||||
end
|
||||
if v.protocol and v.protocol == "_balancing" then
|
||||
balancing_list[#balancing_list + 1] = v
|
||||
end
|
||||
if v.protocol and v.protocol == "_shunt" then
|
||||
shunt_list[#shunt_list + 1] = v
|
||||
end
|
||||
end
|
||||
|
||||
local function get_cfgvalue(shunt_node_id, option)
|
||||
return function(self, section)
|
||||
return m:get(shunt_node_id, option) or "nil"
|
||||
end
|
||||
end
|
||||
local function get_write(shunt_node_id, option)
|
||||
return function(self, section, value)
|
||||
m:set(shunt_node_id, option, value)
|
||||
end
|
||||
end
|
||||
if #normal_list > 0 then
|
||||
for k, v in pairs(shunt_list) do
|
||||
local vid = v.id
|
||||
-- shunt node type, V2ray or Xray
|
||||
local type = s:taboption("Main", ListValue, vid .. "-type", translate("Type"))
|
||||
if has_v2ray then
|
||||
type:value("V2ray", translate("V2ray"))
|
||||
end
|
||||
if has_xray then
|
||||
type:value("Xray", translate("Xray"))
|
||||
end
|
||||
type.cfgvalue = get_cfgvalue(v.id, "type")
|
||||
type.write = get_write(v.id, "type")
|
||||
|
||||
-- pre-proxy
|
||||
o = s:taboption("Main", Flag, vid .. "-preproxy_enabled", translate("Preproxy"))
|
||||
o:depends("node", v.id)
|
||||
o.rmempty = false
|
||||
o.cfgvalue = get_cfgvalue(v.id, "preproxy_enabled")
|
||||
o.write = get_write(v.id, "preproxy_enabled")
|
||||
|
||||
o = s:taboption("Main", Value, vid .. "-main_node", string.format('<a style="color:red">%s</a>', translate("Preproxy Node")), translate("Set the node to be used as a pre-proxy. Each rule (including <code>Default</code>) has a separate switch that controls whether this rule uses the pre-proxy or not."))
|
||||
o:depends(vid .. "-preproxy_enabled", "1")
|
||||
for k1, v1 in pairs(balancing_list) do
|
||||
o:value(v1.id, v1.remark)
|
||||
end
|
||||
for k1, v1 in pairs(normal_list) do
|
||||
o:value(v1.id, v1.remark)
|
||||
end
|
||||
if #o.keylist > 0 then
|
||||
o.default = o.keylist[1]
|
||||
end
|
||||
o.cfgvalue = get_cfgvalue(v.id, "main_node")
|
||||
o.write = get_write(v.id, "main_node")
|
||||
if shunt_remark == "main" and auto_switch_tip then
|
||||
o.description = auto_switch_tip
|
||||
end
|
||||
|
||||
if (has_v2ray and has_xray) or (v.type == "V2ray" and not has_v2ray) or (v.type == "Xray" and not has_xray) then
|
||||
type:depends("node", v.id)
|
||||
else
|
||||
type:depends("node", "hide") --不存在的依赖,即始终隐藏
|
||||
end
|
||||
|
||||
uci:foreach(appname, "shunt_rules", function(e)
|
||||
local id = e[".name"]
|
||||
local node_option = vid .. "-" .. id .. "_node"
|
||||
if id and e.remarks then
|
||||
o = s:taboption("Main", Value, node_option, string.format('* <a href="%s" target="_blank">%s</a>', api.url("shunt_rules", id), e.remarks))
|
||||
o.cfgvalue = get_cfgvalue(v.id, id)
|
||||
o.write = get_write(v.id, id)
|
||||
o:depends("node", v.id)
|
||||
o.default = "nil"
|
||||
o:value("nil", translate("Close"))
|
||||
o:value("_default", translate("Default"))
|
||||
o:value("_direct", translate("Direct Connection"))
|
||||
o:value("_blackhole", translate("Blackhole"))
|
||||
|
||||
local pt = s:taboption("Main", ListValue, vid .. "-".. id .. "_proxy_tag", string.format('* <a style="color:red">%s</a>', e.remarks .. " " .. translate("Preproxy")))
|
||||
pt.cfgvalue = get_cfgvalue(v.id, id .. "_proxy_tag")
|
||||
pt.write = get_write(v.id, id .. "_proxy_tag")
|
||||
pt:value("nil", translate("Close"))
|
||||
pt:value("main", translate("Preproxy Node"))
|
||||
pt.default = "nil"
|
||||
for k1, v1 in pairs(balancing_list) do
|
||||
o:value(v1.id, v1.remark)
|
||||
end
|
||||
for k1, v1 in pairs(normal_list) do
|
||||
o:value(v1.id, v1.remark)
|
||||
pt:depends({ [node_option] = v1.id, [vid .. "-preproxy_enabled"] = "1" })
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
local id = "default_node"
|
||||
o = s:taboption("Main", Value, vid .. "-" .. id, string.format('* <a style="color:red">%s</a>', translate("Default")))
|
||||
o.cfgvalue = get_cfgvalue(v.id, id)
|
||||
o.write = get_write(v.id, id)
|
||||
o:depends("node", v.id)
|
||||
o.default = "_direct"
|
||||
o:value("_direct", translate("Direct Connection"))
|
||||
o:value("_blackhole", translate("Blackhole"))
|
||||
for k1, v1 in pairs(balancing_list) do
|
||||
o:value(v1.id, v1.remark)
|
||||
end
|
||||
for k1, v1 in pairs(normal_list) do
|
||||
o:value(v1.id, v1.remark)
|
||||
end
|
||||
if shunt_remark == "default" and auto_switch_tip then
|
||||
o.description = auto_switch_tip
|
||||
end
|
||||
|
||||
local id = "default_proxy_tag"
|
||||
o = s:taboption("Main", ListValue, vid .. "-" .. id, string.format('* <a style="color:red">%s</a>', translate("Default Preproxy")), translate("When using, localhost will connect this node first and then use this node to connect the default node."))
|
||||
o.cfgvalue = get_cfgvalue(v.id, id)
|
||||
o.write = get_write(v.id, id)
|
||||
o:value("nil", translate("Close"))
|
||||
o:value("main", translate("Preproxy Node"))
|
||||
for k1, v1 in pairs(normal_list) do
|
||||
if v1.protocol ~= "_balancing" then
|
||||
o:depends({ [vid .. "-default_node"] = v1.id, [vid .. "-preproxy_enabled"] = "1" })
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
local tips = s:taboption("Main", DummyValue, "tips", " ")
|
||||
tips.rawhtml = true
|
||||
tips.cfgvalue = function(t, n)
|
||||
return string.format('<a style="color: red">%s</a>', translate("There are no available nodes, please add or subscribe nodes first."))
|
||||
end
|
||||
tips:depends({ node = "nil", ["!reverse"] = true })
|
||||
for k, v in pairs(shunt_list) do
|
||||
tips:depends("node", v.id)
|
||||
end
|
||||
for k, v in pairs(balancing_list) do
|
||||
tips:depends("node", v.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
o = s:taboption("Main", Flag, "localhost_proxy", translate("Localhost Proxy"), translate("When selected, localhost can transparent proxy."))
|
||||
o.default = "1"
|
||||
o.rmempty = false
|
||||
|
||||
node_socks_port = s:taboption("Main", Value, "node_socks_port", translate("Node") .. " Socks " .. translate("Listen Port"))
|
||||
node_socks_port.default = 1070
|
||||
node_socks_port.datatype = "port"
|
||||
|
||||
--[[
|
||||
if has_v2ray or has_xray then
|
||||
node_http_port = s:taboption("Main", Value, "node_http_port", translate("Node") .. " HTTP " .. translate("Listen Port") .. " " .. translate("0 is not use"))
|
||||
node_http_port.default = 0
|
||||
node_http_port.datatype = "port"
|
||||
end
|
||||
]]--
|
||||
|
||||
s:tab("DNS", translate("DNS"))
|
||||
|
||||
o = s:taboption("DNS", ListValue, "direct_dns_protocol", translate("Direct DNS Protocol"))
|
||||
o.default = "auto"
|
||||
o:value("auto", translate("Auto"))
|
||||
o:value("udp", "UDP")
|
||||
o:value("tcp", "TCP")
|
||||
o:value("doh", "DoH")
|
||||
|
||||
---- DNS Forward
|
||||
o = s:taboption("DNS", Value, "direct_dns", translate("Direct DNS"))
|
||||
o.datatype = "or(ipaddr,ipaddrport)"
|
||||
o.default = "119.29.29.29"
|
||||
o:value("114.114.114.114", "114.114.114.114 (114DNS)")
|
||||
o:value("119.29.29.29", "119.29.29.29 (DNSPod)")
|
||||
o:value("223.5.5.5", "223.5.5.5 (AliDNS)")
|
||||
o:depends("direct_dns_protocol", "udp")
|
||||
o:depends("direct_dns_protocol", "tcp")
|
||||
|
||||
---- DoH
|
||||
o = s:taboption("DNS", Value, "direct_dns_doh", translate("Direct DNS DoH"))
|
||||
o.default = "https://223.5.5.5/dns-query"
|
||||
o:value("https://1.12.12.12/dns-query", "DNSPod 1")
|
||||
o:value("https://120.53.53.53/dns-query", "DNSPod 2")
|
||||
o:value("https://223.5.5.5/dns-query", "AliDNS")
|
||||
o.validate = doh_validate
|
||||
o:depends("direct_dns_protocol", "doh")
|
||||
|
||||
o = s:taboption("DNS", Value, "direct_dns_client_ip", translate("Direct DNS EDNS Client Subnet"))
|
||||
o.description = translate("Notify the DNS server when the DNS query is notified, the location of the client (cannot be a private IP address).") .. "<br />" ..
|
||||
translate("This feature requires the DNS server to support the Edns Client Subnet (RFC7871).")
|
||||
o.datatype = "ipaddr"
|
||||
o:depends("direct_dns_protocol", "tcp")
|
||||
o:depends("direct_dns_protocol", "doh")
|
||||
|
||||
o = s:taboption("DNS", ListValue, "direct_dns_query_strategy", translate("Direct Query Strategy"))
|
||||
o.default = "UseIP"
|
||||
o:value("UseIP")
|
||||
o:value("UseIPv4")
|
||||
o:value("UseIPv6")
|
||||
|
||||
o = s:taboption("DNS", ListValue, "remote_dns_protocol", translate("Remote DNS Protocol"))
|
||||
o:value("tcp", "TCP")
|
||||
o:value("doh", "DoH")
|
||||
o:value("udp", "UDP")
|
||||
|
||||
---- DNS Forward
|
||||
o = s:taboption("DNS", Value, "remote_dns", translate("Remote DNS"))
|
||||
o.datatype = "or(ipaddr,ipaddrport)"
|
||||
o.default = "1.1.1.1"
|
||||
o:value("1.1.1.1", "1.1.1.1 (CloudFlare)")
|
||||
o:value("1.1.1.2", "1.1.1.2 (CloudFlare-Security)")
|
||||
o:value("8.8.4.4", "8.8.4.4 (Google)")
|
||||
o:value("8.8.8.8", "8.8.8.8 (Google)")
|
||||
o:value("9.9.9.9", "9.9.9.9 (Quad9-Recommended)")
|
||||
o:value("208.67.220.220", "208.67.220.220 (OpenDNS)")
|
||||
o:value("208.67.222.222", "208.67.222.222 (OpenDNS)")
|
||||
o:depends("remote_dns_protocol", "tcp")
|
||||
o:depends("remote_dns_protocol", "udp")
|
||||
|
||||
---- DoH
|
||||
o = s:taboption("DNS", Value, "remote_dns_doh", translate("Remote DNS DoH"))
|
||||
o.default = "https://1.1.1.1/dns-query"
|
||||
o:value("https://1.1.1.1/dns-query", "CloudFlare")
|
||||
o:value("https://1.1.1.2/dns-query", "CloudFlare-Security")
|
||||
o:value("https://8.8.4.4/dns-query", "Google 8844")
|
||||
o:value("https://8.8.8.8/dns-query", "Google 8888")
|
||||
o:value("https://9.9.9.9/dns-query", "Quad9-Recommended")
|
||||
o:value("https://208.67.222.222/dns-query", "OpenDNS")
|
||||
o:value("https://dns.adguard.com/dns-query,176.103.130.130", "AdGuard")
|
||||
o:value("https://doh.libredns.gr/dns-query,116.202.176.26", "LibreDNS")
|
||||
o:value("https://doh.libredns.gr/ads,116.202.176.26", "LibreDNS (No Ads)")
|
||||
o.validate = doh_validate
|
||||
o:depends("remote_dns_protocol", "doh")
|
||||
|
||||
o = s:taboption("DNS", Value, "remote_dns_client_ip", translate("Remote DNS EDNS Client Subnet"))
|
||||
o.description = translate("Notify the DNS server when the DNS query is notified, the location of the client (cannot be a private IP address).") .. "<br />" ..
|
||||
translate("This feature requires the DNS server to support the Edns Client Subnet (RFC7871).")
|
||||
o.datatype = "ipaddr"
|
||||
o:depends("remote_dns_protocol", "tcp")
|
||||
o:depends("remote_dns_protocol", "doh")
|
||||
|
||||
o = s:taboption("DNS", Flag, "remote_fakedns", "FakeDNS", translate("Use FakeDNS work in the shunt domain that proxy."))
|
||||
o.default = "0"
|
||||
o.rmempty = false
|
||||
|
||||
o = s:taboption("DNS", ListValue, "remote_dns_query_strategy", translate("Remote Query Strategy"))
|
||||
o.default = "UseIPv4"
|
||||
o:value("UseIP")
|
||||
o:value("UseIPv4")
|
||||
o:value("UseIPv6")
|
||||
|
||||
hosts = s:taboption("DNS", TextValue, "dns_hosts", translate("Domain Override"))
|
||||
hosts.rows = 5
|
||||
hosts.wrap = "off"
|
||||
|
||||
o = s:taboption("DNS", Button, "clear_ipset", translate("Clear IPSET"), translate("Try this feature if the rule modification does not take effect."))
|
||||
o.inputstyle = "remove"
|
||||
function o.write(e, e)
|
||||
luci.sys.call("[ -n \"$(nft list sets 2>/dev/null | grep \"passwall2_\")\" ] && sh /usr/share/" .. appname .. "/nftables.sh flush_nftset || sh /usr/share/" .. appname .. "/iptables.sh flush_ipset > /dev/null 2>&1 &")
|
||||
luci.http.redirect(api.url("log"))
|
||||
end
|
||||
|
||||
s:tab("log", translate("Log"))
|
||||
o = s:taboption("log", Flag, "close_log", translate("Close Node Log"))
|
||||
o.rmempty = false
|
||||
|
||||
loglevel = s:taboption("log", ListValue, "loglevel", translate("Log Level"))
|
||||
loglevel.default = "warning"
|
||||
loglevel:value("debug")
|
||||
loglevel:value("info")
|
||||
loglevel:value("warning")
|
||||
loglevel:value("error")
|
||||
|
||||
s:tab("faq", "FAQ")
|
||||
|
||||
o = s:taboption("faq", DummyValue, "")
|
||||
o.template = appname .. "/global/faq"
|
||||
|
||||
-- [[ Socks Server ]]--
|
||||
o = s:taboption("Main", Flag, "socks_enabled", "Socks " .. translate("Main switch"))
|
||||
o.rmempty = false
|
||||
|
||||
s = m:section(TypedSection, "socks", translate("Socks Config"))
|
||||
s.anonymous = true
|
||||
s.addremove = true
|
||||
s.template = "cbi/tblsection"
|
||||
function s.create(e, t)
|
||||
TypedSection.create(e, api.gen_short_uuid())
|
||||
end
|
||||
|
||||
o = s:option(DummyValue, "status", translate("Status"))
|
||||
o.rawhtml = true
|
||||
o.cfgvalue = function(t, n)
|
||||
return string.format('<div class="_status" socks_id="%s"></div>', n)
|
||||
end
|
||||
|
||||
---- Enable
|
||||
o = s:option(Flag, "enabled", translate("Enable"))
|
||||
o.default = 1
|
||||
o.rmempty = false
|
||||
|
||||
socks_node = s:option(ListValue, "node", translate("Socks Node"))
|
||||
|
||||
local n = 1
|
||||
uci:foreach(appname, "socks", function(s)
|
||||
if s[".name"] == section then
|
||||
return false
|
||||
end
|
||||
n = n + 1
|
||||
end)
|
||||
|
||||
o = s:option(Value, "port", "Socks " .. translate("Listen Port"))
|
||||
o.default = n + 1080
|
||||
o.datatype = "port"
|
||||
o.rmempty = false
|
||||
|
||||
if has_v2ray or has_xray then
|
||||
o = s:option(Value, "http_port", "HTTP " .. translate("Listen Port") .. " " .. translate("0 is not use"))
|
||||
o.default = 0
|
||||
o.datatype = "port"
|
||||
end
|
||||
|
||||
for k, v in pairs(nodes_table) do
|
||||
node:value(v.id, v["remark"])
|
||||
if v.type == "Socks" then
|
||||
if has_v2ray or has_xray then
|
||||
socks_node:value(v.id, v["remark"])
|
||||
end
|
||||
else
|
||||
socks_node:value(v.id, v["remark"])
|
||||
end
|
||||
end
|
||||
|
||||
m:append(Template(appname .. "/global/footer"))
|
||||
|
||||
return m
|
140
luci-app-passwall2/luasrc/model/cbi/passwall2/client/haproxy.lua
Normal file
140
luci-app-passwall2/luasrc/model/cbi/passwall2/client/haproxy.lua
Normal file
@ -0,0 +1,140 @@
|
||||
local api = require "luci.passwall2.api"
|
||||
local appname = api.appname
|
||||
local sys = api.sys
|
||||
local net = require "luci.model.network".init()
|
||||
local datatypes = api.datatypes
|
||||
|
||||
local nodes_table = {}
|
||||
for k, e in ipairs(api.get_valid_nodes()) do
|
||||
if e.node_type == "normal" then
|
||||
nodes_table[#nodes_table + 1] = {
|
||||
id = e[".name"],
|
||||
obj = e,
|
||||
remarks = e["remark"]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
m = Map(appname)
|
||||
|
||||
-- [[ Haproxy Settings ]]--
|
||||
s = m:section(TypedSection, "global_haproxy")
|
||||
s.anonymous = true
|
||||
|
||||
s:append(Template(appname .. "/haproxy/status"))
|
||||
|
||||
---- Balancing Enable
|
||||
o = s:option(Flag, "balancing_enable", translate("Enable Load Balancing"))
|
||||
o.rmempty = false
|
||||
o.default = false
|
||||
|
||||
---- Console Username
|
||||
o = s:option(Value, "console_user", translate("Console Username"))
|
||||
o.default = ""
|
||||
o:depends("balancing_enable", true)
|
||||
|
||||
---- Console Password
|
||||
o = s:option(Value, "console_password", translate("Console Password"))
|
||||
o.password = true
|
||||
o.default = ""
|
||||
o:depends("balancing_enable", true)
|
||||
|
||||
---- Console Port
|
||||
o = s:option(Value, "console_port", translate("Console Port"), translate(
|
||||
"In the browser input routing IP plus port access, such as:192.168.1.1:1188"))
|
||||
o.default = "1188"
|
||||
o:depends("balancing_enable", true)
|
||||
|
||||
---- Health Check Type
|
||||
o = s:option(ListValue, "health_check_type", translate("Health Check Type"))
|
||||
o.default = "passwall_logic"
|
||||
o:value("tcp", "TCP")
|
||||
o:value("passwall_logic", translate("Availability test") .. string.format("(passwall %s)", translate("Inner implement")))
|
||||
o:depends("balancing_enable", true)
|
||||
|
||||
---- Health Check Inter
|
||||
o = s:option(Value, "health_check_inter", translate("Health Check Inter"), translate("Units:seconds"))
|
||||
o.default = "60"
|
||||
o:depends("balancing_enable", true)
|
||||
|
||||
o = s:option(DummyValue, "health_check_tips", " ")
|
||||
o.rawhtml = true
|
||||
o.cfgvalue = function(t, n)
|
||||
return string.format('<span style="color: red">%s</span>', translate("When the availability test is used, the load balancing node will be converted into a Socks node. when node list set customizing, must be a Socks node, otherwise the health check will be invalid."))
|
||||
end
|
||||
o:depends("health_check_type", "passwall_logic")
|
||||
|
||||
-- [[ Balancing Settings ]]--
|
||||
s = m:section(TypedSection, "haproxy_config", "",
|
||||
"<font color='red'>" ..
|
||||
translate("Add a node, Export Of Multi WAN Only support Multi Wan. Load specific gravity range 1-256. Multiple primary servers can be load balanced, standby will only be enabled when the primary server is offline! Multiple groups can be set, Haproxy port same one for each group.") ..
|
||||
"\n" .. translate("Note that the node configuration parameters for load balancing must be consistent when use TCP health check type, otherwise it cannot be used normally!") ..
|
||||
"</font>")
|
||||
s.template = "cbi/tblsection"
|
||||
s.sortable = true
|
||||
s.anonymous = true
|
||||
s.addremove = true
|
||||
|
||||
s.create = function(e, t)
|
||||
TypedSection.create(e, api.gen_short_uuid())
|
||||
end
|
||||
|
||||
s.remove = function(self, section)
|
||||
for k, v in pairs(self.children) do
|
||||
v.rmempty = true
|
||||
v.validate = nil
|
||||
end
|
||||
TypedSection.remove(self, section)
|
||||
end
|
||||
|
||||
---- Enable
|
||||
o = s:option(Flag, "enabled", translate("Enable"))
|
||||
o.default = 1
|
||||
o.rmempty = false
|
||||
|
||||
---- Node Address
|
||||
o = s:option(Value, "lbss", translate("Node Address"))
|
||||
for k, v in pairs(nodes_table) do o:value(v.id, v.remarks) end
|
||||
o.rmempty = false
|
||||
o.validate = function(self, value)
|
||||
if not value then return nil end
|
||||
local t = m:get(value) or nil
|
||||
if t and t[".type"] == "nodes" then
|
||||
return value
|
||||
end
|
||||
if datatypes.hostport(value) or datatypes.ip4addrport(value) then
|
||||
return value
|
||||
end
|
||||
if api.is_ipv6addrport(value) then
|
||||
return value
|
||||
end
|
||||
return nil, value
|
||||
end
|
||||
|
||||
---- Haproxy Port
|
||||
o = s:option(Value, "haproxy_port", translate("Haproxy Port"))
|
||||
o.datatype = "port"
|
||||
o.default = 1181
|
||||
o.rmempty = false
|
||||
|
||||
---- Node Weight
|
||||
o = s:option(Value, "lbweight", translate("Node Weight"))
|
||||
o.datatype = "uinteger"
|
||||
o.default = 5
|
||||
o.rmempty = false
|
||||
|
||||
---- Export
|
||||
o = s:option(ListValue, "export", translate("Export Of Multi WAN"))
|
||||
o:value(0, translate("Auto"))
|
||||
local wa = require "luci.tools.webadmin"
|
||||
wa.cbi_add_networks(o)
|
||||
o.default = 0
|
||||
o.rmempty = false
|
||||
|
||||
---- Mode
|
||||
o = s:option(ListValue, "backup", translate("Mode"))
|
||||
o:value(0, translate("Primary"))
|
||||
o:value(1, translate("Standby"))
|
||||
o.rmempty = false
|
||||
|
||||
return m
|
@ -0,0 +1,8 @@
|
||||
local api = require "luci.passwall2.api"
|
||||
local appname = api.appname
|
||||
|
||||
f = SimpleForm(appname)
|
||||
f.reset = false
|
||||
f.submit = false
|
||||
f:append(Template(appname .. "/log/log"))
|
||||
return f
|
@ -0,0 +1,904 @@
|
||||
local api = require "luci.passwall2.api"
|
||||
local appname = api.appname
|
||||
local uci = api.uci
|
||||
|
||||
if not arg[1] or not uci:get(appname, arg[1]) then
|
||||
luci.http.redirect(api.url("node_list"))
|
||||
end
|
||||
|
||||
local ss_encrypt_method_list = {
|
||||
"rc4-md5", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "aes-128-ctr",
|
||||
"aes-192-ctr", "aes-256-ctr", "bf-cfb", "salsa20", "chacha20", "chacha20-ietf",
|
||||
"aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305",
|
||||
"xchacha20-ietf-poly1305"
|
||||
}
|
||||
|
||||
local ss_rust_encrypt_method_list = {
|
||||
"plain", "none",
|
||||
"aes-128-gcm", "aes-256-gcm", "chacha20-ietf-poly1305",
|
||||
"2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha8-poly1305", "2022-blake3-chacha20-poly1305"
|
||||
}
|
||||
|
||||
local ssr_encrypt_method_list = {
|
||||
"none", "table", "rc2-cfb", "rc4", "rc4-md5", "rc4-md5-6", "aes-128-cfb",
|
||||
"aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr",
|
||||
"bf-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb",
|
||||
"cast5-cfb", "des-cfb", "idea-cfb", "seed-cfb", "salsa20", "chacha20",
|
||||
"chacha20-ietf"
|
||||
}
|
||||
|
||||
local ssr_protocol_list = {
|
||||
"origin", "verify_simple", "verify_deflate", "verify_sha1", "auth_simple",
|
||||
"auth_sha1", "auth_sha1_v2", "auth_sha1_v4", "auth_aes128_md5",
|
||||
"auth_aes128_sha1", "auth_chain_a", "auth_chain_b", "auth_chain_c",
|
||||
"auth_chain_d", "auth_chain_e", "auth_chain_f"
|
||||
}
|
||||
local ssr_obfs_list = {
|
||||
"plain", "http_simple", "http_post", "random_head", "tls_simple",
|
||||
"tls1.0_session_auth", "tls1.2_ticket_auth"
|
||||
}
|
||||
|
||||
local v_ss_encrypt_method_list = {
|
||||
"aes-128-gcm", "aes-256-gcm", "chacha20-poly1305"
|
||||
}
|
||||
|
||||
local x_ss_encrypt_method_list = {
|
||||
"aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
|
||||
}
|
||||
|
||||
local security_list = {"none", "auto", "aes-128-gcm", "chacha20-poly1305", "zero"}
|
||||
|
||||
local header_type_list = {
|
||||
"none", "srtp", "utp", "wechat-video", "dtls", "wireguard"
|
||||
}
|
||||
local encrypt_methods_ss_aead = {
|
||||
"chacha20-ietf-poly1305",
|
||||
"aes-128-gcm",
|
||||
"aes-256-gcm",
|
||||
}
|
||||
|
||||
m = Map(appname, translate("Node Config"))
|
||||
m.redirect = api.url()
|
||||
|
||||
s = m:section(NamedSection, arg[1], "nodes", "")
|
||||
s.addremove = false
|
||||
s.dynamic = false
|
||||
|
||||
share = s:option(DummyValue, "passwall2", " ")
|
||||
share.rawhtml = true
|
||||
share.template = "passwall2/node_list/link_share_man"
|
||||
share.value = arg[1]
|
||||
|
||||
remarks = s:option(Value, "remarks", translate("Node Remarks"))
|
||||
remarks.default = translate("Remarks")
|
||||
remarks.rmempty = false
|
||||
|
||||
type = s:option(ListValue, "type", translate("Type"))
|
||||
if api.is_finded("ss-redir") then
|
||||
type:value("SS", translate("Shadowsocks Libev"))
|
||||
end
|
||||
if api.is_finded("sslocal") then
|
||||
type:value("SS-Rust", translate("Shadowsocks Rust"))
|
||||
end
|
||||
if api.is_finded("ssr-redir") then
|
||||
type:value("SSR", translate("ShadowsocksR Libev"))
|
||||
end
|
||||
if api.is_finded("v2ray") then
|
||||
type:value("V2ray", translate("V2ray"))
|
||||
end
|
||||
if api.is_finded("xray") then
|
||||
type:value("Xray", translate("Xray"))
|
||||
end
|
||||
if api.is_finded("brook") then
|
||||
type:value("Brook", translate("Brook"))
|
||||
end
|
||||
if api.is_finded("naive") then
|
||||
type:value("Naiveproxy", translate("NaiveProxy"))
|
||||
end
|
||||
if api.is_finded("hysteria") then
|
||||
type:value("Hysteria", translate("Hysteria"))
|
||||
end
|
||||
|
||||
protocol = s:option(ListValue, "protocol", translate("Protocol"))
|
||||
protocol:value("vmess", translate("Vmess"))
|
||||
protocol:value("vless", translate("VLESS"))
|
||||
protocol:value("http", translate("HTTP"))
|
||||
protocol:value("socks", translate("Socks"))
|
||||
protocol:value("shadowsocks", translate("Shadowsocks"))
|
||||
protocol:value("trojan", translate("Trojan"))
|
||||
protocol:value("wireguard", translate("WireGuard"))
|
||||
protocol:value("_balancing", translate("Balancing"))
|
||||
protocol:value("_shunt", translate("Shunt"))
|
||||
protocol:value("_iface", translate("Custom Interface") .. " (Only Support Xray)")
|
||||
protocol:depends("type", "V2ray")
|
||||
protocol:depends("type", "Xray")
|
||||
|
||||
|
||||
iface = s:option(Value, "iface", translate("Interface"))
|
||||
iface.default = "eth1"
|
||||
iface:depends("protocol", "_iface")
|
||||
|
||||
local nodes_table = {}
|
||||
local balancers_table = {}
|
||||
for k, e in ipairs(api.get_valid_nodes()) do
|
||||
if e.node_type == "normal" then
|
||||
nodes_table[#nodes_table + 1] = {
|
||||
id = e[".name"],
|
||||
remarks = e["remark"]
|
||||
}
|
||||
end
|
||||
if e.protocol == "_balancing" then
|
||||
balancers_table[#balancers_table + 1] = {
|
||||
id = e[".name"],
|
||||
remarks = e["remark"]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
-- 负载均衡列表
|
||||
local balancing_node = s:option(DynamicList, "balancing_node", translate("Load balancing node list"), translate("Load balancing node list, <a target='_blank' href='https://toutyrater.github.io/routing/balance2.html'>document</a>"))
|
||||
for k, v in pairs(nodes_table) do balancing_node:value(v.id, v.remarks) end
|
||||
balancing_node:depends("protocol", "_balancing")
|
||||
|
||||
local balancingStrategy = s:option(ListValue, "balancingStrategy", translate("Balancing Strategy"))
|
||||
balancingStrategy:depends("protocol", "_balancing")
|
||||
balancingStrategy:value("random")
|
||||
balancingStrategy:value("leastPing")
|
||||
balancingStrategy.default = "random"
|
||||
-- 探测地址
|
||||
local useCustomProbeUrl = s:option(Flag, "useCustomProbeUrl", translate("Use Custome Probe URL"), translate("By default the built-in probe URL will be used, enable this option to use a custom probe URL."))
|
||||
useCustomProbeUrl:depends("balancingStrategy", "leastPing")
|
||||
local probeUrl = s:option(Value, "probeUrl", translate("Probe URL"))
|
||||
probeUrl:depends("useCustomProbeUrl", true)
|
||||
probeUrl.default = "https://www.google.com/generate_204"
|
||||
probeUrl.description = translate("The URL used to detect the connection status.")
|
||||
-- 探测间隔
|
||||
local probeInterval = s:option(Value, "probeInterval", translate("Probe Interval"))
|
||||
probeInterval:depends("balancingStrategy", "leastPing")
|
||||
probeInterval.default = "1m"
|
||||
probeInterval.description = translate("The interval between initiating probes. Every time this time elapses, a server status check is performed on a server. The time format is numbers + units, such as '10s', '2h45m', and the supported time units are <code>ns</code>, <code>us</code>, <code>ms</code>, <code>s</code>, <code>m</code>, <code>h</code>, which correspond to nanoseconds, microseconds, milliseconds, seconds, minutes, and hours, respectively.")
|
||||
|
||||
-- 分流
|
||||
if #nodes_table > 0 then
|
||||
o = s:option(Flag, "preproxy_enabled", translate("Preproxy"))
|
||||
o:depends("protocol", "_shunt")
|
||||
o = s:option(Value, "main_node", string.format('<a style="color:red">%s</a>', translate("Preproxy Node")), translate("Set the node to be used as a pre-proxy. Each rule (including <code>Default</code>) has a separate switch that controls whether this rule uses the pre-proxy or not."))
|
||||
o:depends("preproxy_enabled", "1")
|
||||
for k, v in pairs(balancers_table) do
|
||||
o:value(v.id, v.remarks)
|
||||
end
|
||||
for k, v in pairs(nodes_table) do
|
||||
o:value(v.id, v.remarks)
|
||||
end
|
||||
if #o.keylist > 0 then
|
||||
o.default = o.keylist[1]
|
||||
end
|
||||
end
|
||||
uci:foreach(appname, "shunt_rules", function(e)
|
||||
if e[".name"] and e.remarks then
|
||||
o = s:option(Value, e[".name"], string.format('* <a href="%s" target="_blank">%s</a>', api.url("shunt_rules", e[".name"]), e.remarks))
|
||||
o.default = "nil"
|
||||
o:value("nil", translate("Close"))
|
||||
o:value("_default", translate("Default"))
|
||||
o:value("_direct", translate("Direct Connection"))
|
||||
o:value("_blackhole", translate("Blackhole"))
|
||||
o:depends("protocol", "_shunt")
|
||||
|
||||
if #nodes_table > 0 then
|
||||
for k, v in pairs(balancers_table) do
|
||||
o:value(v.id, v.remarks)
|
||||
end
|
||||
local pt = s:option(ListValue, e[".name"] .. "_proxy_tag", string.format('* <a style="color:red">%s</a>', e.remarks .. " " .. translate("Preproxy")))
|
||||
pt:value("nil", translate("Close"))
|
||||
pt:value("main", translate("Preproxy Node"))
|
||||
pt.default = "nil"
|
||||
for k, v in pairs(nodes_table) do
|
||||
o:value(v.id, v.remarks)
|
||||
pt:depends({ preproxy_enabled = "1", [e[".name"]] = v.id })
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
shunt_tips = s:option(DummyValue, "shunt_tips", " ")
|
||||
shunt_tips.rawhtml = true
|
||||
shunt_tips.cfgvalue = function(t, n)
|
||||
return string.format('<a style="color: red" href="../rule">%s</a>', translate("No shunt rules? Click me to go to add."))
|
||||
end
|
||||
shunt_tips:depends("protocol", "_shunt")
|
||||
|
||||
local default_node = s:option(Value, "default_node", string.format('* <a style="color:red">%s</a>', translate("Default")))
|
||||
default_node:depends("protocol", "_shunt")
|
||||
default_node.default = "_direct"
|
||||
default_node:value("_direct", translate("Direct Connection"))
|
||||
default_node:value("_blackhole", translate("Blackhole"))
|
||||
|
||||
if #nodes_table > 0 then
|
||||
for k, v in pairs(balancers_table) do
|
||||
default_node:value(v.id, v.remarks)
|
||||
end
|
||||
local dpt = s:option(ListValue, "default_proxy_tag", string.format('* <a style="color:red">%s</a>', translate("Default Preproxy")), translate("When using, localhost will connect this node first and then use this node to connect the default node."))
|
||||
dpt:value("nil", translate("Close"))
|
||||
dpt:value("main", translate("Preproxy Node"))
|
||||
dpt.default = "nil"
|
||||
for k, v in pairs(nodes_table) do
|
||||
default_node:value(v.id, v.remarks)
|
||||
dpt:depends({ preproxy_enabled = "1", default_node = v.id })
|
||||
end
|
||||
end
|
||||
|
||||
domainStrategy = s:option(ListValue, "domainStrategy", translate("Domain Strategy"))
|
||||
domainStrategy:value("AsIs")
|
||||
domainStrategy:value("IPIfNonMatch")
|
||||
domainStrategy:value("IPOnDemand")
|
||||
domainStrategy.default = "IPOnDemand"
|
||||
domainStrategy.description = "<br /><ul><li>" .. translate("'AsIs': Only use domain for routing. Default value.")
|
||||
.. "</li><li>" .. translate("'IPIfNonMatch': When no rule matches current domain, resolves it into IP addresses (A or AAAA records) and try all rules again.")
|
||||
.. "</li><li>" .. translate("'IPOnDemand': As long as there is a IP-based rule, resolves the domain into IP immediately.")
|
||||
.. "</li></ul>"
|
||||
domainStrategy:depends("protocol", "_shunt")
|
||||
|
||||
domainMatcher = s:option(ListValue, "domainMatcher", translate("Domain matcher"))
|
||||
domainMatcher:value("hybrid")
|
||||
domainMatcher:value("linear")
|
||||
domainMatcher:depends("protocol", "_shunt")
|
||||
|
||||
|
||||
-- Brook协议
|
||||
brook_protocol = s:option(ListValue, "brook_protocol", translate("Protocol"))
|
||||
brook_protocol:value("client", translate("Brook"))
|
||||
brook_protocol:value("wsclient", translate("WebSocket"))
|
||||
brook_protocol:depends("type", "Brook")
|
||||
function brook_protocol.cfgvalue(self, section)
|
||||
return m:get(section, "protocol")
|
||||
end
|
||||
function brook_protocol.write(self, section, value)
|
||||
m:set(section, "protocol", value)
|
||||
end
|
||||
|
||||
brook_tls = s:option(Flag, "brook_tls", translate("Use TLS"))
|
||||
brook_tls:depends("brook_protocol", "wsclient")
|
||||
|
||||
-- Naiveproxy协议
|
||||
naiveproxy_protocol = s:option(ListValue, "naiveproxy_protocol", translate("Protocol"))
|
||||
naiveproxy_protocol:value("https", translate("HTTPS"))
|
||||
naiveproxy_protocol:value("quic", translate("QUIC"))
|
||||
naiveproxy_protocol:depends("type", "Naiveproxy")
|
||||
function naiveproxy_protocol.cfgvalue(self, section)
|
||||
return m:get(section, "protocol")
|
||||
end
|
||||
function naiveproxy_protocol.write(self, section, value)
|
||||
m:set(section, "protocol", value)
|
||||
end
|
||||
|
||||
address = s:option(Value, "address", translate("Address (Support Domain Name)"))
|
||||
address.rmempty = false
|
||||
address:depends("type", "SS")
|
||||
address:depends("type", "SS-Rust")
|
||||
address:depends("type", "SSR")
|
||||
address:depends("type", "Brook")
|
||||
address:depends("type", "Naiveproxy")
|
||||
address:depends("type", "Hysteria")
|
||||
address:depends({ type = "V2ray", protocol = "vmess" })
|
||||
address:depends({ type = "V2ray", protocol = "vless" })
|
||||
address:depends({ type = "V2ray", protocol = "http" })
|
||||
address:depends({ type = "V2ray", protocol = "socks" })
|
||||
address:depends({ type = "V2ray", protocol = "shadowsocks" })
|
||||
address:depends({ type = "V2ray", protocol = "trojan" })
|
||||
address:depends({ type = "Xray", protocol = "vmess" })
|
||||
address:depends({ type = "Xray", protocol = "vless" })
|
||||
address:depends({ type = "Xray", protocol = "http" })
|
||||
address:depends({ type = "Xray", protocol = "socks" })
|
||||
address:depends({ type = "Xray", protocol = "shadowsocks" })
|
||||
address:depends({ type = "Xray", protocol = "trojan" })
|
||||
address:depends({ type = "Xray", protocol = "wireguard" })
|
||||
|
||||
--[[
|
||||
use_ipv6 = s:option(Flag, "use_ipv6", translate("Use IPv6"))
|
||||
use_ipv6.default = 0
|
||||
use_ipv6:depends("type", "SS")
|
||||
use_ipv6:depends("type", "SS-Rust")
|
||||
use_ipv6:depends("type", "SSR")
|
||||
use_ipv6:depends("type", "Brook")
|
||||
use_ipv6:depends("type", "Hysteria")
|
||||
use_ipv6:depends({ type = "V2ray", protocol = "vmess" })
|
||||
use_ipv6:depends({ type = "V2ray", protocol = "vless" })
|
||||
use_ipv6:depends({ type = "V2ray", protocol = "http" })
|
||||
use_ipv6:depends({ type = "V2ray", protocol = "socks" })
|
||||
use_ipv6:depends({ type = "V2ray", protocol = "shadowsocks" })
|
||||
use_ipv6:depends({ type = "V2ray", protocol = "trojan" })
|
||||
use_ipv6:depends({ type = "Xray", protocol = "vmess" })
|
||||
use_ipv6:depends({ type = "Xray", protocol = "vless" })
|
||||
use_ipv6:depends({ type = "Xray", protocol = "http" })
|
||||
use_ipv6:depends({ type = "Xray", protocol = "socks" })
|
||||
use_ipv6:depends({ type = "Xray", protocol = "shadowsocks" })
|
||||
use_ipv6:depends({ type = "Xray", protocol = "trojan" })
|
||||
--]]
|
||||
|
||||
port = s:option(Value, "port", translate("Port"))
|
||||
port.datatype = "port"
|
||||
port.rmempty = false
|
||||
port:depends("type", "SS")
|
||||
port:depends("type", "SS-Rust")
|
||||
port:depends("type", "SSR")
|
||||
port:depends("type", "Brook")
|
||||
port:depends("type", "Naiveproxy")
|
||||
port:depends("type", "Hysteria")
|
||||
port:depends({ type = "V2ray", protocol = "vmess" })
|
||||
port:depends({ type = "V2ray", protocol = "vless" })
|
||||
port:depends({ type = "V2ray", protocol = "http" })
|
||||
port:depends({ type = "V2ray", protocol = "socks" })
|
||||
port:depends({ type = "V2ray", protocol = "shadowsocks" })
|
||||
port:depends({ type = "V2ray", protocol = "trojan" })
|
||||
port:depends({ type = "Xray", protocol = "vmess" })
|
||||
port:depends({ type = "Xray", protocol = "vless" })
|
||||
port:depends({ type = "Xray", protocol = "http" })
|
||||
port:depends({ type = "Xray", protocol = "socks" })
|
||||
port:depends({ type = "Xray", protocol = "shadowsocks" })
|
||||
port:depends({ type = "Xray", protocol = "trojan" })
|
||||
port:depends({ type = "Xray", protocol = "wireguard" })
|
||||
|
||||
hysteria_hop = s:option(Value, "hysteria_hop", translate("Additional ports for hysteria hop"))
|
||||
hysteria_hop:depends("type", "Hysteria")
|
||||
|
||||
username = s:option(Value, "username", translate("Username"))
|
||||
username:depends("type", "Naiveproxy")
|
||||
username:depends({ type = "V2ray", protocol = "http" })
|
||||
username:depends({ type = "V2ray", protocol = "socks" })
|
||||
username:depends({ type = "Xray", protocol = "http" })
|
||||
username:depends({ type = "Xray", protocol = "socks" })
|
||||
|
||||
password = s:option(Value, "password", translate("Password"))
|
||||
password.password = true
|
||||
password:depends("type", "SS")
|
||||
password:depends("type", "SS-Rust")
|
||||
password:depends("type", "SSR")
|
||||
password:depends("type", "Brook")
|
||||
password:depends("type", "Naiveproxy")
|
||||
password:depends({ type = "V2ray", protocol = "http" })
|
||||
password:depends({ type = "V2ray", protocol = "socks" })
|
||||
password:depends({ type = "V2ray", protocol = "shadowsocks" })
|
||||
password:depends({ type = "V2ray", protocol = "trojan" })
|
||||
password:depends({ type = "Xray", protocol = "http" })
|
||||
password:depends({ type = "Xray", protocol = "socks" })
|
||||
password:depends({ type = "Xray", protocol = "shadowsocks" })
|
||||
password:depends({ type = "Xray", protocol = "trojan" })
|
||||
|
||||
hysteria_protocol = s:option(ListValue, "hysteria_protocol", translate("Protocol"))
|
||||
hysteria_protocol:value("udp", "UDP")
|
||||
hysteria_protocol:value("faketcp", "faketcp")
|
||||
hysteria_protocol:value("wechat-video", "wechat-video")
|
||||
hysteria_protocol:depends("type", "Hysteria")
|
||||
function hysteria_protocol.cfgvalue(self, section)
|
||||
return m:get(section, "protocol")
|
||||
end
|
||||
function hysteria_protocol.write(self, section, value)
|
||||
m:set(section, "protocol", value)
|
||||
end
|
||||
|
||||
hysteria_obfs = s:option(Value, "hysteria_obfs", translate("Obfs Password"))
|
||||
hysteria_obfs:depends("type", "Hysteria")
|
||||
|
||||
hysteria_auth_type = s:option(ListValue, "hysteria_auth_type", translate("Auth Type"))
|
||||
hysteria_auth_type:value("disable", translate("Disable"))
|
||||
hysteria_auth_type:value("string", translate("STRING"))
|
||||
hysteria_auth_type:value("base64", translate("BASE64"))
|
||||
hysteria_auth_type:depends("type", "Hysteria")
|
||||
|
||||
hysteria_auth_password = s:option(Value, "hysteria_auth_password", translate("Auth Password"))
|
||||
hysteria_auth_password.password = true
|
||||
hysteria_auth_password:depends("hysteria_auth_type", "string")
|
||||
hysteria_auth_password:depends("hysteria_auth_type", "base64")
|
||||
|
||||
hysteria_alpn = s:option(Value, "hysteria_alpn", translate("QUIC TLS ALPN"))
|
||||
hysteria_alpn:depends("type", "Hysteria")
|
||||
|
||||
ss_encrypt_method = s:option(Value, "ss_encrypt_method", translate("Encrypt Method"))
|
||||
for a, t in ipairs(ss_encrypt_method_list) do ss_encrypt_method:value(t) end
|
||||
ss_encrypt_method:depends("type", "SS")
|
||||
function ss_encrypt_method.cfgvalue(self, section)
|
||||
return m:get(section, "method")
|
||||
end
|
||||
function ss_encrypt_method.write(self, section, value)
|
||||
m:set(section, "method", value)
|
||||
end
|
||||
|
||||
ss_rust_encrypt_method = s:option(Value, "ss_rust_encrypt_method", translate("Encrypt Method"))
|
||||
for a, t in ipairs(ss_rust_encrypt_method_list) do ss_rust_encrypt_method:value(t) end
|
||||
ss_rust_encrypt_method:depends("type", "SS-Rust")
|
||||
function ss_rust_encrypt_method.cfgvalue(self, section)
|
||||
return m:get(section, "method")
|
||||
end
|
||||
function ss_rust_encrypt_method.write(self, section, value)
|
||||
m:set(section, "method", value)
|
||||
end
|
||||
|
||||
ssr_encrypt_method = s:option(Value, "ssr_encrypt_method", translate("Encrypt Method"))
|
||||
for a, t in ipairs(ssr_encrypt_method_list) do ssr_encrypt_method:value(t) end
|
||||
ssr_encrypt_method:depends("type", "SSR")
|
||||
function ssr_encrypt_method.cfgvalue(self, section)
|
||||
return m:get(section, "method")
|
||||
end
|
||||
function ssr_encrypt_method.write(self, section, value)
|
||||
m:set(section, "method", value)
|
||||
end
|
||||
|
||||
security = s:option(ListValue, "security", translate("Encrypt Method"))
|
||||
for a, t in ipairs(security_list) do security:value(t) end
|
||||
security:depends({ type = "V2ray", protocol = "vmess" })
|
||||
security:depends({ type = "Xray", protocol = "vmess" })
|
||||
|
||||
encryption = s:option(Value, "encryption", translate("Encrypt Method"))
|
||||
encryption.default = "none"
|
||||
encryption:value("none")
|
||||
encryption:depends({ type = "V2ray", protocol = "vless" })
|
||||
encryption:depends({ type = "Xray", protocol = "vless" })
|
||||
|
||||
v_ss_encrypt_method = s:option(ListValue, "v_ss_encrypt_method", translate("Encrypt Method"))
|
||||
for a, t in ipairs(v_ss_encrypt_method_list) do v_ss_encrypt_method:value(t) end
|
||||
v_ss_encrypt_method:depends({ type = "V2ray", protocol = "shadowsocks" })
|
||||
function v_ss_encrypt_method.cfgvalue(self, section)
|
||||
return m:get(section, "method")
|
||||
end
|
||||
function v_ss_encrypt_method.write(self, section, value)
|
||||
m:set(section, "method", value)
|
||||
end
|
||||
|
||||
x_ss_encrypt_method = s:option(ListValue, "x_ss_encrypt_method", translate("Encrypt Method"))
|
||||
for a, t in ipairs(x_ss_encrypt_method_list) do x_ss_encrypt_method:value(t) end
|
||||
x_ss_encrypt_method:depends({ type = "Xray", protocol = "shadowsocks" })
|
||||
function x_ss_encrypt_method.cfgvalue(self, section)
|
||||
return m:get(section, "method")
|
||||
end
|
||||
function x_ss_encrypt_method.write(self, section, value)
|
||||
m:set(section, "method", value)
|
||||
end
|
||||
|
||||
iv_check = s:option(Flag, "iv_check", translate("IV Check"))
|
||||
iv_check:depends({ type = "V2ray", protocol = "shadowsocks" })
|
||||
iv_check:depends({ type = "Xray", protocol = "shadowsocks", x_ss_encrypt_method = "aes-128-gcm" })
|
||||
iv_check:depends({ type = "Xray", protocol = "shadowsocks", x_ss_encrypt_method = "aes-256-gcm" })
|
||||
iv_check:depends({ type = "Xray", protocol = "shadowsocks", x_ss_encrypt_method = "chacha20-poly1305" })
|
||||
iv_check:depends({ type = "Xray", protocol = "shadowsocks", x_ss_encrypt_method = "xchacha20-poly1305" })
|
||||
|
||||
uot = s:option(Flag, "uot", translate("UDP over TCP"), translate("Need Xray-core or sing-box as server side."))
|
||||
uot:depends({ type = "Xray", protocol = "shadowsocks", x_ss_encrypt_method = "2022-blake3-aes-128-gcm" })
|
||||
uot:depends({ type = "Xray", protocol = "shadowsocks", x_ss_encrypt_method = "2022-blake3-aes-256-gcm" })
|
||||
uot:depends({ type = "Xray", protocol = "shadowsocks", x_ss_encrypt_method = "2022-blake3-chacha20-poly1305" })
|
||||
|
||||
ssr_protocol = s:option(Value, "ssr_protocol", translate("Protocol"))
|
||||
for a, t in ipairs(ssr_protocol_list) do ssr_protocol:value(t) end
|
||||
ssr_protocol:depends("type", "SSR")
|
||||
function ssr_protocol.cfgvalue(self, section)
|
||||
return m:get(section, "protocol")
|
||||
end
|
||||
function ssr_protocol.write(self, section, value)
|
||||
m:set(section, "protocol", value)
|
||||
end
|
||||
|
||||
protocol_param = s:option(Value, "protocol_param", translate("Protocol_param"))
|
||||
protocol_param:depends("type", "SSR")
|
||||
|
||||
obfs = s:option(Value, "obfs", translate("Obfs"))
|
||||
for a, t in ipairs(ssr_obfs_list) do obfs:value(t) end
|
||||
obfs:depends("type", "SSR")
|
||||
|
||||
obfs_param = s:option(Value, "obfs_param", translate("Obfs_param"))
|
||||
obfs_param:depends("type", "SSR")
|
||||
|
||||
timeout = s:option(Value, "timeout", translate("Connection Timeout"))
|
||||
timeout.datatype = "uinteger"
|
||||
timeout.default = 300
|
||||
timeout:depends("type", "SS")
|
||||
timeout:depends("type", "SS-Rust")
|
||||
timeout:depends("type", "SSR")
|
||||
|
||||
tcp_fast_open = s:option(ListValue, "tcp_fast_open", "TCP " .. translate("Fast Open"), translate("Need node support required"))
|
||||
tcp_fast_open:value("false")
|
||||
tcp_fast_open:value("true")
|
||||
tcp_fast_open:depends("type", "SS")
|
||||
tcp_fast_open:depends("type", "SS-Rust")
|
||||
tcp_fast_open:depends("type", "SSR")
|
||||
|
||||
fast_open = s:option(Flag, "fast_open", translate("Fast Open"))
|
||||
fast_open.default = "0"
|
||||
fast_open:depends("type", "Hysteria")
|
||||
|
||||
ss_plugin = s:option(ListValue, "ss_plugin", translate("plugin"))
|
||||
ss_plugin:value("none", translate("none"))
|
||||
if api.is_finded("xray-plugin") then ss_plugin:value("xray-plugin") end
|
||||
if api.is_finded("v2ray-plugin") then ss_plugin:value("v2ray-plugin") end
|
||||
if api.is_finded("obfs-local") then ss_plugin:value("obfs-local") end
|
||||
ss_plugin:depends("type", "SS")
|
||||
ss_plugin:depends("type", "SS-Rust")
|
||||
function ss_plugin.cfgvalue(self, section)
|
||||
return m:get(section, "plugin")
|
||||
end
|
||||
function ss_plugin.write(self, section, value)
|
||||
m:set(section, "plugin", value)
|
||||
end
|
||||
|
||||
ss_plugin_opts = s:option(Value, "ss_plugin_opts", translate("opts"))
|
||||
ss_plugin_opts:depends("ss_plugin", "xray-plugin")
|
||||
ss_plugin_opts:depends("ss_plugin", "v2ray-plugin")
|
||||
ss_plugin_opts:depends("ss_plugin", "obfs-local")
|
||||
function ss_plugin_opts.cfgvalue(self, section)
|
||||
return m:get(section, "plugin_opts")
|
||||
end
|
||||
function ss_plugin_opts.write(self, section, value)
|
||||
m:set(section, "plugin_opts", value)
|
||||
end
|
||||
|
||||
uuid = s:option(Value, "uuid", translate("ID"))
|
||||
uuid.password = true
|
||||
uuid:depends({ type = "V2ray", protocol = "vmess" })
|
||||
uuid:depends({ type = "V2ray", protocol = "vless" })
|
||||
uuid:depends({ type = "Xray", protocol = "vmess" })
|
||||
uuid:depends({ type = "Xray", protocol = "vless" })
|
||||
|
||||
tls = s:option(Flag, "tls", translate("TLS"))
|
||||
tls.default = 0
|
||||
tls:depends({ type = "V2ray", protocol = "vmess" })
|
||||
tls:depends({ type = "V2ray", protocol = "vless" })
|
||||
tls:depends({ type = "V2ray", protocol = "socks" })
|
||||
tls:depends({ type = "V2ray", protocol = "trojan" })
|
||||
tls:depends({ type = "V2ray", protocol = "shadowsocks" })
|
||||
tls:depends({ type = "Xray", protocol = "vmess" })
|
||||
tls:depends({ type = "Xray", protocol = "vless" })
|
||||
tls:depends({ type = "Xray", protocol = "socks" })
|
||||
tls:depends({ type = "Xray", protocol = "trojan" })
|
||||
tls:depends({ type = "Xray", protocol = "shadowsocks" })
|
||||
|
||||
tlsflow = s:option(Value, "tlsflow", translate("flow"))
|
||||
tlsflow.default = ""
|
||||
tlsflow:value("", translate("Disable"))
|
||||
tlsflow:value("xtls-rprx-vision")
|
||||
tlsflow:value("xtls-rprx-vision-udp443")
|
||||
tlsflow:depends({ type = "Xray", protocol = "vless", tls = true, transport = "tcp" })
|
||||
|
||||
reality = s:option(Flag, "reality", translate("REALITY"), translate("Only recommend to use with VLESS-TCP-XTLS-Vision."))
|
||||
reality.default = 0
|
||||
reality:depends({ type = "Xray", tls = true, transport = "tcp" })
|
||||
reality:depends({ type = "Xray", tls = true, transport = "h2" })
|
||||
reality:depends({ type = "Xray", tls = true, transport = "grpc" })
|
||||
|
||||
alpn = s:option(ListValue, "alpn", translate("alpn"))
|
||||
alpn.default = "default"
|
||||
alpn:value("default", translate("Default"))
|
||||
alpn:value("h2,http/1.1")
|
||||
alpn:value("h2")
|
||||
alpn:value("http/1.1")
|
||||
alpn:depends({ type = "V2ray", tls = true })
|
||||
alpn:depends({ type = "Xray", tls = true, reality = false })
|
||||
|
||||
tls_serverName = s:option(Value, "tls_serverName", translate("Domain"))
|
||||
tls_serverName:depends("tls", true)
|
||||
tls_serverName:depends("type", "Hysteria")
|
||||
|
||||
tls_allowInsecure = s:option(Flag, "tls_allowInsecure", translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."))
|
||||
tls_allowInsecure.default = "0"
|
||||
tls_allowInsecure:depends({ tls = true, reality = false })
|
||||
tls_allowInsecure:depends("type", "Hysteria")
|
||||
|
||||
xray_fingerprint = s:option(Value, "xray_fingerprint", translate("Finger Print"), translate("Avoid using randomized, unless you have to."))
|
||||
xray_fingerprint:value("", translate("Disable"))
|
||||
xray_fingerprint:value("chrome")
|
||||
xray_fingerprint:value("firefox")
|
||||
xray_fingerprint:value("safari")
|
||||
xray_fingerprint:value("ios")
|
||||
--xray_fingerprint:value("android")
|
||||
xray_fingerprint:value("edge")
|
||||
--xray_fingerprint:value("360")
|
||||
xray_fingerprint:value("qq")
|
||||
xray_fingerprint:value("random")
|
||||
xray_fingerprint:value("randomized")
|
||||
xray_fingerprint.default = ""
|
||||
xray_fingerprint:depends({ type = "Xray", tls = true, reality = false })
|
||||
function xray_fingerprint.cfgvalue(self, section)
|
||||
return m:get(section, "fingerprint")
|
||||
end
|
||||
function xray_fingerprint.write(self, section, value)
|
||||
m:set(section, "fingerprint", value)
|
||||
end
|
||||
function xray_fingerprint.remove(self, section)
|
||||
m:del(section, "fingerprint")
|
||||
end
|
||||
|
||||
|
||||
-- [[ REALITY部分 ]] --
|
||||
reality_publicKey = s:option(Value, "reality_publicKey", translate("Public Key"))
|
||||
reality_publicKey:depends({ type = "Xray", tls = true, reality = true })
|
||||
|
||||
reality_shortId = s:option(Value, "reality_shortId", translate("Short Id"))
|
||||
reality_shortId:depends({ type = "Xray", tls = true, reality = true })
|
||||
|
||||
reality_spiderX = s:option(Value, "reality_spiderX", translate("Spider X"))
|
||||
reality_spiderX.placeholder = "/"
|
||||
reality_spiderX:depends({ type = "Xray", tls = true, reality = true })
|
||||
|
||||
reality_fingerprint = s:option(Value, "reality_fingerprint", translate("Finger Print"), translate("Avoid using randomized, unless you have to."))
|
||||
reality_fingerprint:value("chrome")
|
||||
reality_fingerprint:value("firefox")
|
||||
reality_fingerprint:value("safari")
|
||||
reality_fingerprint:value("ios")
|
||||
--reality_fingerprint:value("android")
|
||||
reality_fingerprint:value("edge")
|
||||
--reality_fingerprint:value("360")
|
||||
reality_fingerprint:value("qq")
|
||||
reality_fingerprint:value("random")
|
||||
reality_fingerprint:value("randomized")
|
||||
reality_fingerprint.default = "chrome"
|
||||
reality_fingerprint:depends({ type = "Xray", tls = true, reality = true })
|
||||
function reality_fingerprint.cfgvalue(self, section)
|
||||
return m:get(section, "fingerprint")
|
||||
end
|
||||
function reality_fingerprint.write(self, section, value)
|
||||
m:set(section, "fingerprint", value)
|
||||
end
|
||||
|
||||
transport = s:option(ListValue, "transport", translate("Transport"))
|
||||
transport:value("tcp", "TCP")
|
||||
transport:value("mkcp", "mKCP")
|
||||
transport:value("ws", "WebSocket")
|
||||
transport:value("h2", "HTTP/2")
|
||||
transport:value("ds", "DomainSocket")
|
||||
transport:value("quic", "QUIC")
|
||||
transport:value("grpc", "gRPC")
|
||||
transport:depends({ type = "V2ray", protocol = "vmess" })
|
||||
transport:depends({ type = "V2ray", protocol = "vless" })
|
||||
transport:depends({ type = "V2ray", protocol = "socks" })
|
||||
transport:depends({ type = "V2ray", protocol = "shadowsocks" })
|
||||
transport:depends({ type = "V2ray", protocol = "trojan" })
|
||||
transport:depends({ type = "Xray", protocol = "vmess" })
|
||||
transport:depends({ type = "Xray", protocol = "vless" })
|
||||
transport:depends({ type = "Xray", protocol = "socks" })
|
||||
transport:depends({ type = "Xray", protocol = "shadowsocks" })
|
||||
transport:depends({ type = "Xray", protocol = "trojan" })
|
||||
|
||||
--[[
|
||||
ss_transport = s:option(ListValue, "ss_transport", translate("Transport"))
|
||||
ss_transport:value("ws", "WebSocket")
|
||||
ss_transport:value("h2", "HTTP/2")
|
||||
ss_transport:value("h2+ws", "HTTP/2 & WebSocket")
|
||||
ss_transport:depends({ type = "V2ray", protocol = "shadowsocks" })
|
||||
ss_transport:depends({ type = "Xray", protocol = "shadowsocks" })
|
||||
]]--
|
||||
|
||||
wireguard_public_key = s:option(Value, "wireguard_public_key", translate("Public Key"))
|
||||
wireguard_public_key:depends({ type = "Xray", protocol = "wireguard" })
|
||||
|
||||
wireguard_secret_key = s:option(Value, "wireguard_secret_key", translate("Private Key"))
|
||||
wireguard_secret_key:depends({ type = "Xray", protocol = "wireguard" })
|
||||
|
||||
wireguard_preSharedKey = s:option(Value, "wireguard_preSharedKey", translate("Pre shared key"))
|
||||
wireguard_preSharedKey:depends({ type = "Xray", protocol = "wireguard" })
|
||||
|
||||
wireguard_local_address = s:option(DynamicList, "wireguard_local_address", translate("Local Address"))
|
||||
wireguard_local_address:depends({ type = "Xray", protocol = "wireguard" })
|
||||
|
||||
wireguard_mtu = s:option(Value, "wireguard_mtu", translate("MTU"))
|
||||
wireguard_mtu.default = "1420"
|
||||
wireguard_mtu:depends({ type = "Xray", protocol = "wireguard" })
|
||||
|
||||
if api.compare_versions(api.get_app_version("xray"), ">=", "1.8.0") then
|
||||
wireguard_reserved = s:option(Value, "wireguard_reserved", translate("Reserved"), translate("Decimal numbers separated by \",\" or Base64-encoded strings."))
|
||||
wireguard_reserved:depends({ type = "Xray", protocol = "wireguard" })
|
||||
end
|
||||
|
||||
wireguard_keepAlive = s:option(Value, "wireguard_keepAlive", translate("Keep Alive"))
|
||||
wireguard_keepAlive.default = "0"
|
||||
wireguard_keepAlive:depends({ type = "Xray", protocol = "wireguard" })
|
||||
|
||||
-- [[ TCP部分 ]]--
|
||||
|
||||
-- TCP伪装
|
||||
tcp_guise = s:option(ListValue, "tcp_guise", translate("Camouflage Type"))
|
||||
tcp_guise:value("none", "none")
|
||||
tcp_guise:value("http", "http")
|
||||
tcp_guise:depends("transport", "tcp")
|
||||
|
||||
-- HTTP域名
|
||||
tcp_guise_http_host = s:option(DynamicList, "tcp_guise_http_host", translate("HTTP Host"))
|
||||
tcp_guise_http_host:depends("tcp_guise", "http")
|
||||
|
||||
-- HTTP路径
|
||||
tcp_guise_http_path = s:option(DynamicList, "tcp_guise_http_path", translate("HTTP Path"))
|
||||
tcp_guise_http_path.placeholder = "/"
|
||||
tcp_guise_http_path:depends("tcp_guise", "http")
|
||||
|
||||
-- [[ mKCP部分 ]]--
|
||||
|
||||
mkcp_guise = s:option(ListValue, "mkcp_guise", translate("Camouflage Type"), translate('<br />none: default, no masquerade, data sent is packets with no characteristics.<br />srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).<br />utp: packets disguised as uTP will be recognized as bittorrent downloaded data.<br />wechat-video: packets disguised as WeChat video calls.<br />dtls: disguised as DTLS 1.2 packet.<br />wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)'))
|
||||
for a, t in ipairs(header_type_list) do mkcp_guise:value(t) end
|
||||
mkcp_guise:depends("transport", "mkcp")
|
||||
|
||||
mkcp_mtu = s:option(Value, "mkcp_mtu", translate("KCP MTU"))
|
||||
mkcp_mtu.default = "1350"
|
||||
mkcp_mtu:depends("transport", "mkcp")
|
||||
|
||||
mkcp_tti = s:option(Value, "mkcp_tti", translate("KCP TTI"))
|
||||
mkcp_tti.default = "20"
|
||||
mkcp_tti:depends("transport", "mkcp")
|
||||
|
||||
mkcp_uplinkCapacity = s:option(Value, "mkcp_uplinkCapacity", translate("KCP uplinkCapacity"))
|
||||
mkcp_uplinkCapacity.default = "5"
|
||||
mkcp_uplinkCapacity:depends("transport", "mkcp")
|
||||
|
||||
mkcp_downlinkCapacity = s:option(Value, "mkcp_downlinkCapacity", translate("KCP downlinkCapacity"))
|
||||
mkcp_downlinkCapacity.default = "20"
|
||||
mkcp_downlinkCapacity:depends("transport", "mkcp")
|
||||
|
||||
mkcp_congestion = s:option(Flag, "mkcp_congestion", translate("KCP Congestion"))
|
||||
mkcp_congestion:depends("transport", "mkcp")
|
||||
|
||||
mkcp_readBufferSize = s:option(Value, "mkcp_readBufferSize", translate("KCP readBufferSize"))
|
||||
mkcp_readBufferSize.default = "1"
|
||||
mkcp_readBufferSize:depends("transport", "mkcp")
|
||||
|
||||
mkcp_writeBufferSize = s:option(Value, "mkcp_writeBufferSize", translate("KCP writeBufferSize"))
|
||||
mkcp_writeBufferSize.default = "1"
|
||||
mkcp_writeBufferSize:depends("transport", "mkcp")
|
||||
|
||||
mkcp_seed = s:option(Value, "mkcp_seed", translate("KCP Seed"))
|
||||
mkcp_seed:depends("transport", "mkcp")
|
||||
|
||||
-- [[ WebSocket部分 ]]--
|
||||
ws_host = s:option(Value, "ws_host", translate("WebSocket Host"))
|
||||
ws_host:depends("transport", "ws")
|
||||
ws_host:depends("ss_transport", "ws")
|
||||
|
||||
ws_path = s:option(Value, "ws_path", translate("WebSocket Path"))
|
||||
ws_path.placeholder = "/"
|
||||
ws_path:depends("transport", "ws")
|
||||
ws_path:depends("ss_transport", "ws")
|
||||
ws_path:depends({ type = "Brook", brook_protocol = "wsclient" })
|
||||
|
||||
ws_enableEarlyData = s:option(Flag, "ws_enableEarlyData", translate("Enable early data"))
|
||||
ws_enableEarlyData:depends({ type = "V2ray", transport = "ws" })
|
||||
|
||||
ws_maxEarlyData = s:option(Value, "ws_maxEarlyData", translate("Early data length"))
|
||||
ws_maxEarlyData.default = "1024"
|
||||
ws_maxEarlyData:depends("ws_enableEarlyData", true)
|
||||
|
||||
ws_earlyDataHeaderName = s:option(Value, "ws_earlyDataHeaderName", translate("Early data header name"), translate("Recommended value: Sec-WebSocket-Protocol"))
|
||||
ws_earlyDataHeaderName:depends("ws_enableEarlyData", true)
|
||||
|
||||
-- [[ HTTP/2部分 ]]--
|
||||
h2_host = s:option(Value, "h2_host", translate("HTTP/2 Host"))
|
||||
h2_host:depends("transport", "h2")
|
||||
h2_host:depends("ss_transport", "h2")
|
||||
|
||||
h2_path = s:option(Value, "h2_path", translate("HTTP/2 Path"))
|
||||
h2_path.placeholder = "/"
|
||||
h2_path:depends("transport", "h2")
|
||||
h2_path:depends("ss_transport", "h2")
|
||||
|
||||
h2_health_check = s:option(Flag, "h2_health_check", translate("Health check"))
|
||||
h2_health_check:depends({ type = "Xray", transport = "h2"})
|
||||
|
||||
h2_read_idle_timeout = s:option(Value, "h2_read_idle_timeout", translate("Idle timeout"))
|
||||
h2_read_idle_timeout.default = "10"
|
||||
h2_read_idle_timeout:depends("h2_health_check", true)
|
||||
|
||||
h2_health_check_timeout = s:option(Value, "h2_health_check_timeout", translate("Health check timeout"))
|
||||
h2_health_check_timeout.default = "15"
|
||||
h2_health_check_timeout:depends("h2_health_check", true)
|
||||
|
||||
-- [[ DomainSocket部分 ]]--
|
||||
ds_path = s:option(Value, "ds_path", "Path", translate("A legal file path. This file must not exist before running."))
|
||||
ds_path:depends("transport", "ds")
|
||||
|
||||
-- [[ QUIC部分 ]]--
|
||||
quic_security = s:option(ListValue, "quic_security", translate("Encrypt Method"))
|
||||
quic_security:value("none")
|
||||
quic_security:value("aes-128-gcm")
|
||||
quic_security:value("chacha20-poly1305")
|
||||
quic_security:depends("transport", "quic")
|
||||
|
||||
quic_key = s:option(Value, "quic_key", translate("Encrypt Method") .. translate("Key"))
|
||||
quic_key:depends("transport", "quic")
|
||||
|
||||
quic_guise = s:option(ListValue, "quic_guise", translate("Camouflage Type"))
|
||||
for a, t in ipairs(header_type_list) do quic_guise:value(t) end
|
||||
quic_guise:depends("transport", "quic")
|
||||
|
||||
-- [[ gRPC部分 ]]--
|
||||
grpc_serviceName = s:option(Value, "grpc_serviceName", "ServiceName")
|
||||
grpc_serviceName:depends("transport", "grpc")
|
||||
|
||||
grpc_mode = s:option(ListValue, "grpc_mode", "gRPC " .. translate("Transfer mode"))
|
||||
grpc_mode:value("gun")
|
||||
grpc_mode:value("multi")
|
||||
grpc_mode:depends({ type = "Xray", transport = "grpc"})
|
||||
|
||||
grpc_health_check = s:option(Flag, "grpc_health_check", translate("Health check"))
|
||||
grpc_health_check:depends({ type = "Xray", transport = "grpc"})
|
||||
|
||||
grpc_idle_timeout = s:option(Value, "grpc_idle_timeout", translate("Idle timeout"))
|
||||
grpc_idle_timeout.default = "10"
|
||||
grpc_idle_timeout:depends("grpc_health_check", true)
|
||||
|
||||
grpc_health_check_timeout = s:option(Value, "grpc_health_check_timeout", translate("Health check timeout"))
|
||||
grpc_health_check_timeout.default = "20"
|
||||
grpc_health_check_timeout:depends("grpc_health_check", true)
|
||||
|
||||
grpc_permit_without_stream = s:option(Flag, "grpc_permit_without_stream", translate("Permit without stream"))
|
||||
grpc_permit_without_stream.default = "0"
|
||||
grpc_permit_without_stream:depends("grpc_health_check", true)
|
||||
|
||||
grpc_initial_windows_size = s:option(Value, "grpc_initial_windows_size", translate("Initial Windows Size"))
|
||||
grpc_initial_windows_size.default = "0"
|
||||
grpc_initial_windows_size:depends({ type = "Xray", transport = "grpc"})
|
||||
|
||||
-- [[ Mux ]]--
|
||||
mux = s:option(Flag, "mux", translate("Mux"))
|
||||
mux:depends({ type = "V2ray", protocol = "vmess" })
|
||||
mux:depends({ type = "V2ray", protocol = "vless" })
|
||||
mux:depends({ type = "V2ray", protocol = "http" })
|
||||
mux:depends({ type = "V2ray", protocol = "socks" })
|
||||
mux:depends({ type = "V2ray", protocol = "shadowsocks" })
|
||||
mux:depends({ type = "V2ray", protocol = "trojan" })
|
||||
mux:depends({ type = "Xray", protocol = "vmess" })
|
||||
mux:depends({ type = "Xray", protocol = "vless", tlsflow = "" })
|
||||
mux:depends({ type = "Xray", protocol = "http" })
|
||||
mux:depends({ type = "Xray", protocol = "socks" })
|
||||
mux:depends({ type = "Xray", protocol = "shadowsocks" })
|
||||
mux:depends({ type = "Xray", protocol = "trojan" })
|
||||
|
||||
-- [[ XUDP Mux ]]--
|
||||
xmux = s:option(Flag, "xmux", translate("Mux"))
|
||||
xmux.default = 1
|
||||
xmux:depends({ type = "Xray", protocol = "vless", tlsflow = "xtls-rprx-vision" })
|
||||
xmux:depends({ type = "Xray", protocol = "vless", tlsflow = "xtls-rprx-vision-udp443" })
|
||||
|
||||
mux_concurrency = s:option(Value, "mux_concurrency", translate("Mux concurrency"))
|
||||
mux_concurrency.default = 8
|
||||
mux_concurrency:depends("mux", true)
|
||||
mux_concurrency:depends("smux", true)
|
||||
|
||||
xudp_concurrency = s:option(Value, "xudp_concurrency", translate("XUDP Mux concurrency"))
|
||||
xudp_concurrency.default = 8
|
||||
xudp_concurrency:depends("xmux", true)
|
||||
|
||||
smux_idle_timeout = s:option(Value, "smux_idle_timeout", translate("Mux idle timeout"))
|
||||
smux_idle_timeout.default = 60
|
||||
smux_idle_timeout:depends("smux", true)
|
||||
|
||||
hysteria_up_mbps = s:option(Value, "hysteria_up_mbps", translate("Max upload Mbps"))
|
||||
hysteria_up_mbps.default = "10"
|
||||
hysteria_up_mbps:depends("type", "Hysteria")
|
||||
|
||||
hysteria_down_mbps = s:option(Value, "hysteria_down_mbps", translate("Max download Mbps"))
|
||||
hysteria_down_mbps.default = "50"
|
||||
hysteria_down_mbps:depends("type", "Hysteria")
|
||||
|
||||
hysteria_recv_window_conn = s:option(Value, "hysteria_recv_window_conn", translate("QUIC stream receive window"))
|
||||
hysteria_recv_window_conn:depends("type", "Hysteria")
|
||||
|
||||
hysteria_recv_window = s:option(Value, "hysteria_recv_window", translate("QUIC connection receive window"))
|
||||
hysteria_recv_window:depends("type", "Hysteria")
|
||||
|
||||
hysteria_handshake_timeout = s:option(Value, "hysteria_handshake_timeout", translate("Handshake Timeout"))
|
||||
hysteria_handshake_timeout:depends("type", "Hysteria")
|
||||
|
||||
hysteria_idle_timeout = s:option(Value, "hysteria_idle_timeout", translate("Idle Timeout"))
|
||||
hysteria_idle_timeout:depends("type", "Hysteria")
|
||||
|
||||
hysteria_hop_interval = s:option(Value, "hysteria_hop_interval", translate("Hop Interval"))
|
||||
hysteria_hop_interval:depends("type", "Hysteria")
|
||||
|
||||
hysteria_disable_mtu_discovery = s:option(Flag, "hysteria_disable_mtu_discovery", translate("Disable MTU detection"))
|
||||
hysteria_disable_mtu_discovery:depends("type", "Hysteria")
|
||||
|
||||
hysteria_lazy_start = s:option(Flag, "hysteria_lazy_start", translate("Lazy Start"))
|
||||
hysteria_lazy_start:depends("type", "Hysteria")
|
||||
|
||||
protocol.validate = function(self, value)
|
||||
if value == "_shunt" or value == "_balancing" then
|
||||
address.rmempty = true
|
||||
port.rmempty = true
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
return m
|
@ -0,0 +1,140 @@
|
||||
local api = require "luci.passwall2.api"
|
||||
local appname = api.appname
|
||||
local sys = api.sys
|
||||
local datatypes = api.datatypes
|
||||
|
||||
m = Map(appname)
|
||||
|
||||
-- [[ Other Settings ]]--
|
||||
s = m:section(TypedSection, "global_other")
|
||||
s.anonymous = true
|
||||
|
||||
o = s:option(MultiValue, "nodes_ping", " ")
|
||||
o:value("auto_ping", translate("Auto Ping"), translate("This will automatically ping the node for latency"))
|
||||
o:value("tcping", translate("Tcping"), translate("This will use tcping replace ping detection of node"))
|
||||
o:value("info", translate("Show server address and port"), translate("Show server address and port"))
|
||||
|
||||
-- [[ Add the node via the link ]]--
|
||||
s:append(Template(appname .. "/node_list/link_add_node"))
|
||||
|
||||
local nodes_ping = m:get("@global_other[0]", "nodes_ping") or ""
|
||||
|
||||
-- [[ Node List ]]--
|
||||
s = m:section(TypedSection, "nodes")
|
||||
s.anonymous = true
|
||||
s.addremove = true
|
||||
s.template = "cbi/tblsection"
|
||||
s.extedit = api.url("node_config", "%s")
|
||||
function s.create(e, t)
|
||||
local uuid = api.gen_short_uuid()
|
||||
t = uuid
|
||||
TypedSection.create(e, t)
|
||||
luci.http.redirect(e.extedit:format(t))
|
||||
end
|
||||
|
||||
function s.remove(e, t)
|
||||
m.uci:foreach(appname, "socks", function(s)
|
||||
if s["node"] == t then
|
||||
m:del(s[".name"])
|
||||
end
|
||||
end)
|
||||
m.uci:foreach(appname, "acl_rule", function(s)
|
||||
if s["node"] and s["node"] == t then
|
||||
m:set(s[".name"], "node", "default")
|
||||
end
|
||||
end)
|
||||
for k, v in ipairs(m:get("@auto_switch[0]", "node") or {}) do
|
||||
if v and v == t then
|
||||
sys.call(string.format("uci -q del_list %s.@auto_switch[0].node='%s'", appname, v))
|
||||
end
|
||||
end
|
||||
TypedSection.remove(e, t)
|
||||
local new_node = "nil"
|
||||
local node0 = m:get("@nodes[0]") or nil
|
||||
if node0 then
|
||||
new_node = node0[".name"]
|
||||
end
|
||||
if (m:get("@global[0]", "node") or "nil") == t then
|
||||
m:set('@global[0]', "node", new_node)
|
||||
end
|
||||
end
|
||||
|
||||
s.sortable = true
|
||||
-- 简洁模式
|
||||
o = s:option(DummyValue, "add_from", "")
|
||||
o.cfgvalue = function(t, n)
|
||||
local v = Value.cfgvalue(t, n)
|
||||
if v and v ~= '' then
|
||||
local group = m:get(n, "group") or ""
|
||||
if group ~= "" then
|
||||
v = v .. " " .. group
|
||||
end
|
||||
return v
|
||||
else
|
||||
return ''
|
||||
end
|
||||
end
|
||||
o = s:option(DummyValue, "remarks", translate("Remarks"))
|
||||
o.rawhtml = true
|
||||
o.cfgvalue = function(t, n)
|
||||
local str = ""
|
||||
local is_sub = m:get(n, "is_sub") or ""
|
||||
local group = m:get(n, "group") or ""
|
||||
local remarks = m:get(n, "remarks") or ""
|
||||
local type = m:get(n, "type") or ""
|
||||
str = str .. string.format("<input type='hidden' id='cbid.%s.%s.type' value='%s'/>", appname, n, type)
|
||||
if type == "V2ray" or type == "Xray" then
|
||||
local protocol = m:get(n, "protocol")
|
||||
if protocol == "_balancing" then
|
||||
protocol = translate("Balancing")
|
||||
elseif protocol == "_shunt" then
|
||||
protocol = translate("Shunt")
|
||||
elseif protocol == "vmess" then
|
||||
protocol = "VMess"
|
||||
elseif protocol == "vless" then
|
||||
protocol = "VLESS"
|
||||
else
|
||||
protocol = protocol:gsub("^%l",string.upper)
|
||||
end
|
||||
type = type .. " " .. protocol
|
||||
end
|
||||
local address = m:get(n, "address") or ""
|
||||
local port = m:get(n, "port") or ""
|
||||
str = str .. translate(type) .. ":" .. remarks
|
||||
if address ~= "" and port ~= "" then
|
||||
if nodes_ping:find("info") then
|
||||
if datatypes.ip6addr(address) then
|
||||
str = str .. string.format("([%s]:%s)", address, port)
|
||||
else
|
||||
str = str .. string.format("(%s:%s)", address, port)
|
||||
end
|
||||
end
|
||||
str = str .. string.format("<input type='hidden' id='cbid.%s.%s.address' value='%s'/>", appname, n, address)
|
||||
str = str .. string.format("<input type='hidden' id='cbid.%s.%s.port' value='%s'/>", appname, n, port)
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
---- Ping
|
||||
o = s:option(DummyValue, "ping")
|
||||
o.width = "8%"
|
||||
o.rawhtml = true
|
||||
o.cfgvalue = function(t, n)
|
||||
local result = "---"
|
||||
if not nodes_ping:find("auto_ping") then
|
||||
result = string.format('<span class="ping"><a href="javascript:void(0)" onclick="javascript:ping_node(\'%s\',this)">Ping</a></span>', n)
|
||||
else
|
||||
result = string.format('<span class="ping_value" cbiid="%s">---</span>', n)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
o = s:option(DummyValue, "_url_test")
|
||||
o.rawhtml = true
|
||||
o.cfgvalue = function(t, n)
|
||||
return string.format('<input type="button" class="cbi-button" value="%s" onclick="javascript:urltest_node(\'%s\',this)"', translate("Availability test"), n)
|
||||
end
|
||||
|
||||
m:append(Template(appname .. "/node_list/node_list"))
|
||||
|
||||
return m
|
@ -0,0 +1,119 @@
|
||||
local api = require "luci.passwall2.api"
|
||||
local appname = api.appname
|
||||
local has_ss = api.is_finded("ss-redir")
|
||||
local has_ss_rust = api.is_finded("sslocal")
|
||||
local has_v2ray = api.is_finded("v2ray")
|
||||
local has_xray = api.is_finded("xray")
|
||||
local ss_aead_type = {}
|
||||
if has_ss then
|
||||
ss_aead_type[#ss_aead_type + 1] = "shadowsocks-libev"
|
||||
end
|
||||
if has_ss_rust then
|
||||
ss_aead_type[#ss_aead_type + 1] = "shadowsocks-rust"
|
||||
end
|
||||
if has_v2ray then
|
||||
ss_aead_type[#ss_aead_type + 1] = "v2ray"
|
||||
end
|
||||
if has_xray then
|
||||
ss_aead_type[#ss_aead_type + 1] = "xray"
|
||||
end
|
||||
|
||||
m = Map(appname)
|
||||
|
||||
-- [[ Subscribe Settings ]]--
|
||||
s = m:section(TypedSection, "global_subscribe", "")
|
||||
s.anonymous = true
|
||||
|
||||
o = s:option(ListValue, "filter_keyword_mode", translate("Filter keyword Mode"))
|
||||
o:value("0", translate("Close"))
|
||||
o:value("1", translate("Discard List"))
|
||||
o:value("2", translate("Keep List"))
|
||||
o:value("3", translate("Discard List,But Keep List First"))
|
||||
o:value("4", translate("Keep List,But Discard List First"))
|
||||
|
||||
o = s:option(DynamicList, "filter_discard_list", translate("Discard List"))
|
||||
|
||||
o = s:option(DynamicList, "filter_keep_list", translate("Keep List"))
|
||||
|
||||
if #ss_aead_type > 0 then
|
||||
o = s:option(ListValue, "ss_aead_type", translate("SS AEAD Node Use Type"))
|
||||
for key, value in pairs(ss_aead_type) do
|
||||
o:value(value, translate(value:gsub("^%l",string.upper)))
|
||||
end
|
||||
end
|
||||
|
||||
---- Subscribe Delete All
|
||||
o = s:option(Button, "_stop", translate("Delete All Subscribe Node"))
|
||||
o.inputstyle = "remove"
|
||||
function o.write(e, e)
|
||||
luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua truncate > /dev/null 2>&1")
|
||||
end
|
||||
|
||||
o = s:option(Button, "_update", translate("Manual subscription All"))
|
||||
o.inputstyle = "apply"
|
||||
function o.write(t, n)
|
||||
luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua start > /dev/null 2>&1 &")
|
||||
luci.http.redirect(api.url("log"))
|
||||
end
|
||||
|
||||
s = m:section(TypedSection, "subscribe_list", "", "<font color='red'>" .. translate("Please input the subscription url first, save and submit before manual subscription.") .. "</font>")
|
||||
s.addremove = true
|
||||
s.anonymous = true
|
||||
s.sortable = true
|
||||
s.template = "cbi/tblsection"
|
||||
s.extedit = api.url("node_subscribe_config", "%s")
|
||||
function s.create(e, t)
|
||||
local id = TypedSection.create(e, t)
|
||||
luci.http.redirect(e.extedit:format(id))
|
||||
end
|
||||
|
||||
o = s:option(Value, "remark", translate("Remarks"))
|
||||
o.width = "auto"
|
||||
o.rmempty = false
|
||||
o.validate = function(self, value, t)
|
||||
if value then
|
||||
local count = 0
|
||||
m.uci:foreach(appname, "subscribe_list", function(e)
|
||||
if e[".name"] ~= t and e["remark"] == value then
|
||||
count = count + 1
|
||||
end
|
||||
end)
|
||||
if count > 0 then
|
||||
return nil, translate("This remark already exists, please change a new remark.")
|
||||
end
|
||||
return value
|
||||
end
|
||||
end
|
||||
|
||||
o = s:option(DummyValue, "_node_count")
|
||||
o.rawhtml = true
|
||||
o.cfgvalue = function(t, n)
|
||||
local remark = m:get(n, "remark") or ""
|
||||
local num = 0
|
||||
m.uci:foreach(appname, "nodes", function(s)
|
||||
if s["add_from"] ~= "" and s["add_from"] == remark then
|
||||
num = num + 1
|
||||
end
|
||||
end)
|
||||
return string.format("<span title='%s' style='color:red'>%s</span>", remark .. " " .. translate("Node num") .. ": " .. num, num)
|
||||
end
|
||||
|
||||
o = s:option(Value, "url", translate("Subscribe URL"))
|
||||
o.width = "auto"
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(Button, "_remove", translate("Delete the subscribed node"))
|
||||
o.inputstyle = "remove"
|
||||
function o.write(t, n)
|
||||
local remark = m:get(n, "remark") or ""
|
||||
luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua truncate " .. remark .. " > /dev/null 2>&1")
|
||||
end
|
||||
|
||||
o = s:option(Button, "_update", translate("Manual subscription"))
|
||||
o.inputstyle = "apply"
|
||||
function o.write(t, n)
|
||||
luci.sys.call("lua /usr/share/" .. appname .. "/subscribe.lua start " .. n .. " > /dev/null 2>&1 &")
|
||||
luci.http.redirect(api.url("log"))
|
||||
end
|
||||
|
||||
return m
|
@ -0,0 +1,90 @@
|
||||
local api = require "luci.passwall2.api"
|
||||
local appname = api.appname
|
||||
local sys = api.sys
|
||||
local has_ss = api.is_finded("ss-redir")
|
||||
local has_ss_rust = api.is_finded("sslocal")
|
||||
local has_v2ray = api.is_finded("v2ray")
|
||||
local has_xray = api.is_finded("xray")
|
||||
local ss_aead_type = {}
|
||||
if has_ss then
|
||||
ss_aead_type[#ss_aead_type + 1] = "shadowsocks-libev"
|
||||
end
|
||||
if has_ss_rust then
|
||||
ss_aead_type[#ss_aead_type + 1] = "shadowsocks-rust"
|
||||
end
|
||||
if has_v2ray then
|
||||
ss_aead_type[#ss_aead_type + 1] = "v2ray"
|
||||
end
|
||||
if has_xray then
|
||||
ss_aead_type[#ss_aead_type + 1] = "xray"
|
||||
end
|
||||
|
||||
m = Map(appname)
|
||||
m.redirect = api.url("node_subscribe")
|
||||
|
||||
s = m:section(NamedSection, arg[1])
|
||||
s.addremove = false
|
||||
s.dynamic = false
|
||||
|
||||
o = s:option(Value, "remark", translate("Subscribe Remark"))
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(TextValue, "url", translate("Subscribe URL"))
|
||||
o.rows = 5
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(Flag, "allowInsecure", translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."))
|
||||
o.default = "1"
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(ListValue, "filter_keyword_mode", translate("Filter keyword Mode"))
|
||||
o.default = "5"
|
||||
o:value("0", translate("Close"))
|
||||
o:value("1", translate("Discard List"))
|
||||
o:value("2", translate("Keep List"))
|
||||
o:value("3", translate("Discard List,But Keep List First"))
|
||||
o:value("4", translate("Keep List,But Discard List First"))
|
||||
o:value("5", translate("Use global config"))
|
||||
|
||||
o = s:option(DynamicList, "filter_discard_list", translate("Discard List"))
|
||||
o:depends("filter_keyword_mode", "1")
|
||||
o:depends("filter_keyword_mode", "3")
|
||||
o:depends("filter_keyword_mode", "4")
|
||||
|
||||
o = s:option(DynamicList, "filter_keep_list", translate("Keep List"))
|
||||
o:depends("filter_keyword_mode", "2")
|
||||
o:depends("filter_keyword_mode", "3")
|
||||
o:depends("filter_keyword_mode", "4")
|
||||
|
||||
if #ss_aead_type > 0 then
|
||||
o = s:option(ListValue, "ss_aead_type", translate("SS AEAD Node Use Type"))
|
||||
o.default = "global"
|
||||
o:value("global", translate("Use global config"))
|
||||
for key, value in pairs(ss_aead_type) do
|
||||
o:value(value, translate(value:gsub("^%l",string.upper)))
|
||||
end
|
||||
end
|
||||
|
||||
---- Enable auto update subscribe
|
||||
o = s:option(Flag, "auto_update", translate("Enable auto update subscribe"))
|
||||
o.default = 0
|
||||
o.rmempty = false
|
||||
|
||||
---- Week update rules
|
||||
o = s:option(ListValue, "week_update", translate("Week update rules"))
|
||||
o:value(7, translate("Every day"))
|
||||
for e = 1, 6 do o:value(e, translate("Week") .. e) end
|
||||
o:value(0, translate("Week") .. translate("day"))
|
||||
o.default = 0
|
||||
o:depends("auto_update", true)
|
||||
|
||||
---- Day update rules
|
||||
o = s:option(ListValue, "time_update", translate("Day update rules"))
|
||||
for e = 0, 23 do o:value(e, e .. translate("oclock")) end
|
||||
o.default = 0
|
||||
o:depends("auto_update", true)
|
||||
|
||||
o = s:option(Value, "user_agent", translate("User-Agent"))
|
||||
o.default = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36"
|
||||
|
||||
return m
|
158
luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua
Normal file
158
luci-app-passwall2/luasrc/model/cbi/passwall2/client/other.lua
Normal file
@ -0,0 +1,158 @@
|
||||
local api = require "luci.passwall2.api"
|
||||
local appname = api.appname
|
||||
local fs = api.fs
|
||||
local has_v2ray = api.is_finded("v2ray")
|
||||
local has_xray = api.is_finded("xray")
|
||||
local has_fw3 = api.is_finded("fw3")
|
||||
local has_fw4 = api.is_finded("fw4")
|
||||
|
||||
m = Map(appname)
|
||||
|
||||
-- [[ Delay Settings ]]--
|
||||
s = m:section(TypedSection, "global_delay", translate("Delay Settings"))
|
||||
s.anonymous = true
|
||||
s.addremove = false
|
||||
|
||||
---- Delay Start
|
||||
o = s:option(Value, "start_delay", translate("Delay Start"), translate("Units:seconds"))
|
||||
o.default = "1"
|
||||
o.rmempty = true
|
||||
|
||||
---- Open and close Daemon
|
||||
o = s:option(Flag, "start_daemon", translate("Open and close Daemon"))
|
||||
o.default = 1
|
||||
o.rmempty = false
|
||||
|
||||
--[[
|
||||
---- Open and close automatically
|
||||
o = s:option(Flag, "auto_on", translate("Open and close automatically"))
|
||||
o.default = 0
|
||||
o.rmempty = false
|
||||
|
||||
---- Automatically turn off time
|
||||
o = s:option(ListValue, "time_off", translate("Automatically turn off time"))
|
||||
o.default = nil
|
||||
o:depends("auto_on", true)
|
||||
o:value(nil, translate("Disable"))
|
||||
for e = 0, 23 do o:value(e, e .. translate("oclock")) end
|
||||
|
||||
---- Automatically turn on time
|
||||
o = s:option(ListValue, "time_on", translate("Automatically turn on time"))
|
||||
o.default = nil
|
||||
o:depends("auto_on", true)
|
||||
o:value(nil, translate("Disable"))
|
||||
for e = 0, 23 do o:value(e, e .. translate("oclock")) end
|
||||
|
||||
---- Automatically restart time
|
||||
o = s:option(ListValue, "time_restart", translate("Automatically restart time"))
|
||||
o.default = nil
|
||||
o:depends("auto_on", true)
|
||||
o:value(nil, translate("Disable"))
|
||||
for e = 0, 23 do o:value(e, e .. translate("oclock")) end
|
||||
--]]
|
||||
|
||||
-- [[ Forwarding Settings ]]--
|
||||
s = m:section(TypedSection, "global_forwarding", translate("Forwarding Settings"))
|
||||
s.anonymous = true
|
||||
s.addremove = false
|
||||
|
||||
---- TCP No Redir Ports
|
||||
o = s:option(Value, "tcp_no_redir_ports", translate("TCP No Redir Ports"))
|
||||
o.default = "disable"
|
||||
o:value("disable", translate("No patterns are used"))
|
||||
o:value("1:65535", translate("All"))
|
||||
|
||||
---- UDP No Redir Ports
|
||||
o = s:option(Value, "udp_no_redir_ports", translate("UDP No Redir Ports"),
|
||||
"<font color='red'>" ..
|
||||
translate("Fill in the ports you don't want to be forwarded by the agent, with the highest priority.") ..
|
||||
"</font>")
|
||||
o.default = "disable"
|
||||
o:value("disable", translate("No patterns are used"))
|
||||
o:value("1:65535", translate("All"))
|
||||
|
||||
---- TCP Redir Ports
|
||||
o = s:option(Value, "tcp_redir_ports", translate("TCP Redir Ports"))
|
||||
o.default = "22,25,53,143,465,587,853,993,995,80,443"
|
||||
o:value("1:65535", translate("All"))
|
||||
o:value("22,25,53,143,465,587,853,993,995,80,443", translate("Common Use"))
|
||||
o:value("80,443", translate("Only Web"))
|
||||
|
||||
---- UDP Redir Ports
|
||||
o = s:option(Value, "udp_redir_ports", translate("UDP Redir Ports"))
|
||||
o.default = "1:65535"
|
||||
o:value("1:65535", translate("All"))
|
||||
|
||||
---- Use nftables
|
||||
o = s:option(ListValue, "use_nft", translate("Firewall tools"))
|
||||
o.default = "0"
|
||||
if has_fw3 then
|
||||
o:value("0", "IPtables")
|
||||
end
|
||||
if has_fw4 then
|
||||
o:value("1", "NFtables")
|
||||
end
|
||||
|
||||
if (os.execute("lsmod | grep -i REDIRECT >/dev/null") == 0 and os.execute("lsmod | grep -i TPROXY >/dev/null") == 0) or (os.execute("lsmod | grep -i nft_redir >/dev/null") == 0 and os.execute("lsmod | grep -i nft_tproxy >/dev/null") == 0) then
|
||||
o = s:option(ListValue, "tcp_proxy_way", translate("TCP Proxy Way"))
|
||||
o.default = "redirect"
|
||||
o:value("redirect", "REDIRECT")
|
||||
o:value("tproxy", "TPROXY")
|
||||
o:depends("ipv6_tproxy", false)
|
||||
|
||||
o = s:option(ListValue, "_tcp_proxy_way", translate("TCP Proxy Way"))
|
||||
o.default = "tproxy"
|
||||
o:value("tproxy", "TPROXY")
|
||||
o:depends("ipv6_tproxy", true)
|
||||
o.write = function(self, section, value)
|
||||
return self.map:set(section, "tcp_proxy_way", value)
|
||||
end
|
||||
|
||||
if os.execute("lsmod | grep -i ip6table_mangle >/dev/null") == 0 or os.execute("lsmod | grep -i nft_tproxy >/dev/null") == 0 then
|
||||
---- IPv6 TProxy
|
||||
o = s:option(Flag, "ipv6_tproxy", translate("IPv6 TProxy"),
|
||||
"<font color='red'>" ..
|
||||
translate("Experimental feature. Make sure that your node supports IPv6.") ..
|
||||
"</font>")
|
||||
o.default = 0
|
||||
o.rmempty = false
|
||||
end
|
||||
end
|
||||
|
||||
o = s:option(Flag, "accept_icmp", translate("Hijacking ICMP (PING)"))
|
||||
o.default = 0
|
||||
|
||||
o = s:option(Flag, "accept_icmpv6", translate("Hijacking ICMPv6 (IPv6 PING)"))
|
||||
o:depends("ipv6_tproxy", true)
|
||||
o.default = 0
|
||||
|
||||
if has_v2ray or has_xray then
|
||||
o = s:option(Flag, "sniffing", translate("Sniffing (V2Ray/Xray)"), translate("When using the V2ray/Xray shunt, must be enabled, otherwise the shunt will invalid."))
|
||||
o.default = 1
|
||||
o.rmempty = false
|
||||
|
||||
if has_xray then
|
||||
route_only = s:option(Flag, "route_only", translate("Sniffing Route Only (Xray)"), translate("When enabled, the server not will resolve the domain name again."))
|
||||
route_only.default = 0
|
||||
route_only:depends("sniffing", true)
|
||||
|
||||
local domains_excluded = string.format("/usr/share/%s/domains_excluded", appname)
|
||||
o = s:option(TextValue, "no_sniffing_hosts", translate("No Sniffing Lists"), translate("Hosts added into No Sniffing Lists will not resolve again on server (Xray only)."))
|
||||
o.rows = 15
|
||||
o.wrap = "off"
|
||||
o.cfgvalue = function(self, section) return fs.readfile(domains_excluded) or "" end
|
||||
o.write = function(self, section, value) fs.writefile(domains_excluded, value:gsub("\r\n", "\n")) end
|
||||
o.remove = function(self, section, value)
|
||||
if route_only:formvalue(section) == "0" then
|
||||
fs.writefile(domains_excluded, "")
|
||||
end
|
||||
end
|
||||
o:depends({sniffing = true, route_only = false})
|
||||
|
||||
o = s:option(Value, "buffer_size", translate("Buffer Size (Xray)"), translate("Buffer size for every connection (kB)"))
|
||||
o.rmempty = true
|
||||
o.datatype = "uinteger"
|
||||
end
|
||||
end
|
||||
|
||||
return m
|
@ -0,0 +1,65 @@
|
||||
local api = require "luci.passwall2.api"
|
||||
local appname = api.appname
|
||||
|
||||
m = Map(appname)
|
||||
-- [[ Rule Settings ]]--
|
||||
s = m:section(TypedSection, "global_rules", translate("Rule status"))
|
||||
s.anonymous = true
|
||||
|
||||
s:append(Template(appname .. "/rule/rule_version"))
|
||||
|
||||
---- Auto Update
|
||||
o = s:option(Flag, "auto_update", translate("Enable auto update rules"))
|
||||
o.default = 0
|
||||
o.rmempty = false
|
||||
|
||||
---- Week Update
|
||||
o = s:option(ListValue, "week_update", translate("Week update rules"))
|
||||
o:value(7, translate("Every day"))
|
||||
for e = 1, 6 do o:value(e, translate("Week") .. e) end
|
||||
o:value(0, translate("Week") .. translate("day"))
|
||||
o.default = 0
|
||||
o:depends("auto_update", true)
|
||||
|
||||
---- Time Update
|
||||
o = s:option(ListValue, "time_update", translate("Day update rules"))
|
||||
for e = 0, 23 do o:value(e, e .. translate("oclock")) end
|
||||
o.default = 0
|
||||
o:depends("auto_update", true)
|
||||
|
||||
o = s:option(Value, "v2ray_location_asset", translate("Location of V2ray/Xray asset"), translate("This variable specifies a directory where geoip.dat and geosite.dat files are."))
|
||||
o.default = "/usr/share/v2ray/"
|
||||
o.rmempty = false
|
||||
|
||||
---- Custom geo file url
|
||||
o = s:option(Value, "geoip_url", translate("Custom geoip URL"))
|
||||
o.default = "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest"
|
||||
o.rmempty = false
|
||||
|
||||
o = s:option(Value, "geosite_url", translate("Custom geosite URL"))
|
||||
o.default = "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest"
|
||||
o.rmempty = false
|
||||
----
|
||||
|
||||
s = m:section(TypedSection, "shunt_rules", "V2ray/Xray " .. translate("Shunt Rule"), "<a style='color: red'>" .. translate("Please note attention to the priority, the higher the order, the higher the priority.") .. "</a>")
|
||||
s.template = "cbi/tblsection"
|
||||
s.anonymous = false
|
||||
s.addremove = true
|
||||
s.sortable = true
|
||||
s.extedit = api.url("shunt_rules", "%s")
|
||||
function s.create(e, t)
|
||||
TypedSection.create(e, t)
|
||||
luci.http.redirect(e.extedit:format(t))
|
||||
end
|
||||
function s.remove(e, t)
|
||||
m.uci:foreach(appname, "nodes", function(s)
|
||||
if s["protocol"] and s["protocol"] == "_shunt" then
|
||||
m:del(s[".name"], t)
|
||||
end
|
||||
end)
|
||||
TypedSection.remove(e, t)
|
||||
end
|
||||
|
||||
o = s:option(DummyValue, "remarks", translate("Remarks"))
|
||||
|
||||
return m
|
@ -0,0 +1,158 @@
|
||||
local api = require "luci.passwall2.api"
|
||||
local appname = api.appname
|
||||
local datatypes = api.datatypes
|
||||
|
||||
m = Map(appname, "V2ray/Xray " .. translate("Shunt Rule"))
|
||||
m.redirect = api.url()
|
||||
|
||||
s = m:section(NamedSection, arg[1], "shunt_rules", "")
|
||||
s.addremove = false
|
||||
s.dynamic = false
|
||||
|
||||
remarks = s:option(Value, "remarks", translate("Remarks"))
|
||||
remarks.default = arg[1]
|
||||
remarks.rmempty = false
|
||||
|
||||
protocol = s:option(MultiValue, "protocol", translate("Protocol"))
|
||||
protocol:value("http")
|
||||
protocol:value("tls")
|
||||
protocol:value("bittorrent")
|
||||
|
||||
network = s:option(ListValue, "network", translate("Network"))
|
||||
network:value("tcp,udp", "TCP UDP")
|
||||
network:value("tcp", "TCP")
|
||||
network:value("udp", "UDP")
|
||||
|
||||
source = s:option(DynamicList, "source", translate("Source"))
|
||||
source.description = "<ul><li>" .. translate("Example:")
|
||||
.. "</li><li>" .. translate("IP") .. ": 192.168.1.100"
|
||||
.. "</li><li>" .. translate("IP CIDR") .. ": 192.168.1.0/24"
|
||||
.. "</li><li>" .. translate("GeoIP") .. ": geoip:private"
|
||||
.. "</li></ul>"
|
||||
source.cast = "string"
|
||||
source.cfgvalue = function(self, section)
|
||||
local value
|
||||
if self.tag_error[section] then
|
||||
value = self:formvalue(section)
|
||||
else
|
||||
value = self.map:get(section, self.option)
|
||||
if type(value) == "string" then
|
||||
local value2 = {}
|
||||
string.gsub(value, '[^' .. " " .. ']+', function(w) table.insert(value2, w) end)
|
||||
value = value2
|
||||
end
|
||||
end
|
||||
return value
|
||||
end
|
||||
source.validate = function(self, value, t)
|
||||
local err = {}
|
||||
for _, v in ipairs(value) do
|
||||
local flag = false
|
||||
if datatypes.ip4addr(v) then
|
||||
flag = true
|
||||
end
|
||||
|
||||
if flag == false and v:find("geoip:") and v:find("geoip:") == 1 then
|
||||
flag = true
|
||||
end
|
||||
|
||||
if flag == false then
|
||||
err[#err + 1] = v
|
||||
end
|
||||
end
|
||||
|
||||
if #err > 0 then
|
||||
self:add_error(t, "invalid", translate("Not true format, please re-enter!"))
|
||||
for _, v in ipairs(err) do
|
||||
self:add_error(t, "invalid", v)
|
||||
end
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
local dynamicList_write = function(self, section, value)
|
||||
local t = {}
|
||||
local t2 = {}
|
||||
if type(value) == "table" then
|
||||
local x
|
||||
for _, x in ipairs(value) do
|
||||
if x and #x > 0 then
|
||||
if not t2[x] then
|
||||
t2[x] = x
|
||||
t[#t+1] = x
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
t = { value }
|
||||
end
|
||||
t = table.concat(t, " ")
|
||||
return DynamicList.write(self, section, t)
|
||||
end
|
||||
|
||||
source.write = dynamicList_write
|
||||
|
||||
sourcePort = s:option(Value, "sourcePort", translate("Source port"))
|
||||
|
||||
port = s:option(Value, "port", translate("port"))
|
||||
|
||||
domain_list = s:option(TextValue, "domain_list", translate("Domain"))
|
||||
domain_list.rows = 10
|
||||
domain_list.wrap = "off"
|
||||
domain_list.validate = function(self, value)
|
||||
local hosts= {}
|
||||
string.gsub(value, '[^' .. "\r\n" .. ']+', function(w) table.insert(hosts, w) end)
|
||||
for index, host in ipairs(hosts) do
|
||||
local flag = 1
|
||||
local tmp_host = host
|
||||
if host:find("regexp:") and host:find("regexp:") == 1 then
|
||||
flag = 0
|
||||
elseif host:find("domain:.") and host:find("domain:.") == 1 then
|
||||
tmp_host = host:gsub("domain:", "")
|
||||
elseif host:find("full:.") and host:find("full:.") == 1 then
|
||||
tmp_host = host:gsub("full:", "")
|
||||
elseif host:find("geosite:") and host:find("geosite:") == 1 then
|
||||
flag = 0
|
||||
elseif host:find("ext:") and host:find("ext:") == 1 then
|
||||
flag = 0
|
||||
end
|
||||
if flag == 1 then
|
||||
if not datatypes.hostname(tmp_host) then
|
||||
return nil, tmp_host .. " " .. translate("Not valid domain name, please re-enter!")
|
||||
end
|
||||
end
|
||||
end
|
||||
return value
|
||||
end
|
||||
domain_list.description = "<br /><ul><li>" .. translate("Plaintext: If this string matches any part of the targeting domain, this rule takes effet. Example: rule 'sina.com' matches targeting domain 'sina.com', 'sina.com.cn' and 'www.sina.com', but not 'sina.cn'.")
|
||||
.. "</li><li>" .. translate("Regular expression: Begining with 'regexp:', the rest is a regular expression. When the regexp matches targeting domain, this rule takes effect. Example: rule 'regexp:\\.goo.*\\.com$' matches 'www.google.com' and 'fonts.googleapis.com', but not 'google.com'.")
|
||||
.. "</li><li>" .. translate("Subdomain (recommended): Begining with 'domain:' and the rest is a domain. When the targeting domain is exactly the value, or is a subdomain of the value, this rule takes effect. Example: rule 'domain:v2ray.com' matches 'www.v2ray.com', 'v2ray.com', but not 'xv2ray.com'.")
|
||||
.. "</li><li>" .. translate("Full domain: Begining with 'full:' and the rest is a domain. When the targeting domain is exactly the value, the rule takes effect. Example: rule 'domain:v2ray.com' matches 'v2ray.com', but not 'www.v2ray.com'.")
|
||||
.. "</li><li>" .. translate("Pre-defined domain list: Begining with 'geosite:' and the rest is a name, such as geosite:google or geosite:cn.")
|
||||
.. "</li><li>" .. translate("Domains from file: Such as 'ext:file:tag'. The value must begin with ext: (lowercase), and followed by filename and tag. The file is placed in resource directory, and has the same format of geosite.dat. The tag must exist in the file.")
|
||||
.. "</li></ul>"
|
||||
ip_list = s:option(TextValue, "ip_list", "IP")
|
||||
ip_list.rows = 10
|
||||
ip_list.wrap = "off"
|
||||
ip_list.validate = function(self, value)
|
||||
local ipmasks= {}
|
||||
string.gsub(value, '[^' .. "\r\n" .. ']+', function(w) table.insert(ipmasks, w) end)
|
||||
for index, ipmask in ipairs(ipmasks) do
|
||||
if ipmask:find("geoip:") and ipmask:find("geoip:") == 1 then
|
||||
elseif ipmask:find("ext:") and ipmask:find("ext:") == 1 then
|
||||
else
|
||||
if not (datatypes.ipmask4(ipmask) or datatypes.ipmask6(ipmask)) then
|
||||
return nil, ipmask .. " " .. translate("Not valid IP format, please re-enter!")
|
||||
end
|
||||
end
|
||||
end
|
||||
return value
|
||||
end
|
||||
ip_list.description = "<br /><ul><li>" .. translate("IP: such as '127.0.0.1'.")
|
||||
.. "</li><li>" .. translate("CIDR: such as '127.0.0.0/8'.")
|
||||
.. "</li><li>" .. translate("GeoIP: such as 'geoip:cn'. It begins with geoip: (lower case) and followed by two letter of country code.")
|
||||
.. "</li><li>" .. translate("IPs from file: Such as 'ext:file:tag'. The value must begin with ext: (lowercase), and followed by filename and tag. The file is placed in resource directory, and has the same format of geoip.dat. The tag must exist in the file.")
|
||||
.. "</li></ul>"
|
||||
|
||||
return m
|
@ -0,0 +1,73 @@
|
||||
local api = require "luci.passwall2.api"
|
||||
|
||||
m = Map("passwall2_server", translate("Server-Side"))
|
||||
|
||||
t = m:section(NamedSection, "global", "global")
|
||||
t.anonymous = true
|
||||
t.addremove = false
|
||||
|
||||
e = t:option(Flag, "enable", translate("Enable"))
|
||||
e.rmempty = false
|
||||
|
||||
t = m:section(TypedSection, "user", translate("Users Manager"))
|
||||
t.anonymous = true
|
||||
t.addremove = true
|
||||
t.sortable = true
|
||||
t.template = "cbi/tblsection"
|
||||
t.extedit = api.url("server_user", "%s")
|
||||
function t.create(e, t)
|
||||
local uuid = api.gen_uuid()
|
||||
t = uuid
|
||||
TypedSection.create(e, t)
|
||||
luci.http.redirect(e.extedit:format(t))
|
||||
end
|
||||
function t.remove(e, t)
|
||||
e.map.proceed = true
|
||||
e.map:del(t)
|
||||
luci.http.redirect(api.url("server"))
|
||||
end
|
||||
|
||||
e = t:option(Flag, "enable", translate("Enable"))
|
||||
e.width = "5%"
|
||||
e.rmempty = false
|
||||
|
||||
e = t:option(DummyValue, "status", translate("Status"))
|
||||
e.rawhtml = true
|
||||
e.cfgvalue = function(t, n)
|
||||
return string.format('<font class="_users_status">%s</font>', translate("Collecting data..."))
|
||||
end
|
||||
|
||||
e = t:option(DummyValue, "remarks", translate("Remarks"))
|
||||
e.width = "15%"
|
||||
|
||||
---- Type
|
||||
e = t:option(DummyValue, "type", translate("Type"))
|
||||
e.cfgvalue = function(t, n)
|
||||
local v = Value.cfgvalue(t, n)
|
||||
if v then
|
||||
if v == "V2ray" or v == "Xray" then
|
||||
local protocol = m:get(n, "protocol")
|
||||
if protocol == "vmess" then
|
||||
protocol = "VMess"
|
||||
elseif protocol == "vless" then
|
||||
protocol = "VLESS"
|
||||
else
|
||||
protocol = protocol:gsub("^%l",string.upper)
|
||||
end
|
||||
return v .. " -> " .. protocol
|
||||
end
|
||||
return v
|
||||
end
|
||||
end
|
||||
|
||||
e = t:option(DummyValue, "port", translate("Port"))
|
||||
|
||||
e = t:option(Flag, "log", translate("Log"))
|
||||
e.default = "1"
|
||||
e.rmempty = false
|
||||
|
||||
m:append(Template("passwall2/server/log"))
|
||||
|
||||
m:append(Template("passwall2/server/users_list_status"))
|
||||
return m
|
||||
|
632
luci-app-passwall2/luasrc/model/cbi/passwall2/server/user.lua
Normal file
632
luci-app-passwall2/luasrc/model/cbi/passwall2/server/user.lua
Normal file
@ -0,0 +1,632 @@
|
||||
local api = require "luci.passwall2.api"
|
||||
|
||||
local ss_encrypt_method_list = {
|
||||
"rc4-md5", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "aes-128-ctr",
|
||||
"aes-192-ctr", "aes-256-ctr", "bf-cfb", "camellia-128-cfb",
|
||||
"camellia-192-cfb", "camellia-256-cfb", "salsa20", "chacha20",
|
||||
"chacha20-ietf", -- aead
|
||||
"aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305",
|
||||
"xchacha20-ietf-poly1305"
|
||||
}
|
||||
|
||||
local ss_rust_encrypt_method_list = {
|
||||
"plain", "none",
|
||||
"aes-128-gcm", "aes-256-gcm", "chacha20-ietf-poly1305",
|
||||
"2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha8-poly1305", "2022-blake3-chacha20-poly1305"
|
||||
}
|
||||
|
||||
local ssr_encrypt_method_list = {
|
||||
"none", "table", "rc2-cfb", "rc4", "rc4-md5", "rc4-md5-6", "aes-128-cfb",
|
||||
"aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr",
|
||||
"bf-cfb", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb",
|
||||
"cast5-cfb", "des-cfb", "idea-cfb", "seed-cfb", "salsa20", "chacha20",
|
||||
"chacha20-ietf"
|
||||
}
|
||||
|
||||
local ssr_protocol_list = {
|
||||
"origin", "verify_simple", "verify_deflate", "verify_sha1", "auth_simple",
|
||||
"auth_sha1", "auth_sha1_v2", "auth_sha1_v4", "auth_aes128_md5",
|
||||
"auth_aes128_sha1", "auth_chain_a", "auth_chain_b", "auth_chain_c",
|
||||
"auth_chain_d", "auth_chain_e", "auth_chain_f"
|
||||
}
|
||||
local ssr_obfs_list = {
|
||||
"plain", "http_simple", "http_post", "random_head", "tls_simple",
|
||||
"tls1.0_session_auth", "tls1.2_ticket_auth"
|
||||
}
|
||||
|
||||
local v_ss_encrypt_method_list = {
|
||||
"aes-128-gcm", "aes-256-gcm", "chacha20-poly1305"
|
||||
}
|
||||
|
||||
local x_ss_encrypt_method_list = {
|
||||
"aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
|
||||
}
|
||||
|
||||
local header_type_list = {
|
||||
"none", "srtp", "utp", "wechat-video", "dtls", "wireguard"
|
||||
}
|
||||
|
||||
local encrypt_methods_ss_aead = {
|
||||
"chacha20-ietf-poly1305",
|
||||
"aes-128-gcm",
|
||||
"aes-256-gcm",
|
||||
}
|
||||
|
||||
m = Map("passwall2_server", translate("Server Config"))
|
||||
m.redirect = api.url("server")
|
||||
|
||||
s = m:section(NamedSection, arg[1], "user", "")
|
||||
s.addremove = false
|
||||
s.dynamic = false
|
||||
|
||||
enable = s:option(Flag, "enable", translate("Enable"))
|
||||
enable.default = "1"
|
||||
enable.rmempty = false
|
||||
|
||||
remarks = s:option(Value, "remarks", translate("Remarks"))
|
||||
remarks.default = translate("Remarks")
|
||||
remarks.rmempty = false
|
||||
|
||||
type = s:option(ListValue, "type", translate("Type"))
|
||||
if api.is_finded("ss-server") then
|
||||
type:value("SS", translate("Shadowsocks"))
|
||||
end
|
||||
if api.is_finded("ssserver") then
|
||||
type:value("SS-Rust", translate("Shadowsocks Rust"))
|
||||
end
|
||||
if api.is_finded("ssr-server") then
|
||||
type:value("SSR", translate("ShadowsocksR"))
|
||||
end
|
||||
if api.is_finded("v2ray") then
|
||||
type:value("V2ray", translate("V2ray"))
|
||||
end
|
||||
if api.is_finded("xray") then
|
||||
type:value("Xray", translate("Xray"))
|
||||
end
|
||||
if api.is_finded("brook") then
|
||||
type:value("Brook", translate("Brook"))
|
||||
end
|
||||
if api.is_finded("hysteria") then
|
||||
type:value("Hysteria", translate("Hysteria"))
|
||||
end
|
||||
|
||||
protocol = s:option(ListValue, "protocol", translate("Protocol"))
|
||||
protocol:value("vmess", "Vmess")
|
||||
protocol:value("vless", "VLESS")
|
||||
protocol:value("http", "HTTP")
|
||||
protocol:value("socks", "Socks")
|
||||
protocol:value("shadowsocks", "Shadowsocks")
|
||||
protocol:value("trojan", "Trojan")
|
||||
protocol:value("mtproto", "MTProto")
|
||||
protocol:value("dokodemo-door", "dokodemo-door")
|
||||
protocol:depends("type", "V2ray")
|
||||
protocol:depends("type", "Xray")
|
||||
|
||||
-- Brook协议
|
||||
brook_protocol = s:option(ListValue, "brook_protocol", translate("Protocol"))
|
||||
brook_protocol:value("server", "Brook")
|
||||
brook_protocol:value("wsserver", "WebSocket")
|
||||
brook_protocol:depends("type", "Brook")
|
||||
function brook_protocol.cfgvalue(self, section)
|
||||
return m:get(section, "protocol")
|
||||
end
|
||||
function brook_protocol.write(self, section, value)
|
||||
m:set(section, "protocol", value)
|
||||
end
|
||||
|
||||
--brook_tls = s:option(Flag, "brook_tls", translate("Use TLS"))
|
||||
--brook_tls:depends("brook_protocol", "wsserver")
|
||||
|
||||
port = s:option(Value, "port", translate("Listen Port"))
|
||||
port.datatype = "port"
|
||||
port.rmempty = false
|
||||
|
||||
auth = s:option(Flag, "auth", translate("Auth"))
|
||||
auth.validate = function(self, value, t)
|
||||
if value and value == "1" then
|
||||
local user_v = username:formvalue(t) or ""
|
||||
local pass_v = password:formvalue(t) or ""
|
||||
if user_v == "" or pass_v == "" then
|
||||
return nil, translate("Username and Password must be used together!")
|
||||
end
|
||||
end
|
||||
return value
|
||||
end
|
||||
auth:depends({ type = "V2ray", protocol = "socks" })
|
||||
auth:depends({ type = "V2ray", protocol = "http" })
|
||||
auth:depends({ type = "Xray", protocol = "socks" })
|
||||
auth:depends({ type = "Xray", protocol = "http" })
|
||||
|
||||
username = s:option(Value, "username", translate("Username"))
|
||||
username:depends("auth", true)
|
||||
|
||||
password = s:option(Value, "password", translate("Password"))
|
||||
password.password = true
|
||||
password:depends("auth", true)
|
||||
password:depends("type", "SS")
|
||||
password:depends("type", "SS-Rust")
|
||||
password:depends("type", "SSR")
|
||||
password:depends("type", "Brook")
|
||||
password:depends({ type = "V2ray", protocol = "shadowsocks" })
|
||||
password:depends({ type = "Xray", protocol = "shadowsocks" })
|
||||
|
||||
mtproto_password = s:option(Value, "mtproto_password", translate("Password"), translate("The MTProto protocol must be 32 characters and can only contain characters from 0 to 9 and a to f."))
|
||||
mtproto_password:depends({ type = "V2ray", protocol = "mtproto" })
|
||||
mtproto_password:depends({ type = "Xray", protocol = "mtproto" })
|
||||
mtproto_password.default = arg[1]
|
||||
function mtproto_password.cfgvalue(self, section)
|
||||
return m:get(section, "password")
|
||||
end
|
||||
function mtproto_password.write(self, section, value)
|
||||
m:set(section, "password", value)
|
||||
end
|
||||
|
||||
d_protocol = s:option(ListValue, "d_protocol", translate("Destination protocol"))
|
||||
d_protocol:value("tcp", "TCP")
|
||||
d_protocol:value("udp", "UDP")
|
||||
d_protocol:value("tcp,udp", "TCP,UDP")
|
||||
d_protocol:depends({ type = "V2ray", protocol = "dokodemo-door" })
|
||||
d_protocol:depends({ type = "Xray", protocol = "dokodemo-door" })
|
||||
|
||||
d_address = s:option(Value, "d_address", translate("Destination address"))
|
||||
d_address:depends({ type = "V2ray", protocol = "dokodemo-door" })
|
||||
d_address:depends({ type = "Xray", protocol = "dokodemo-door" })
|
||||
|
||||
d_port = s:option(Value, "d_port", translate("Destination port"))
|
||||
d_port.datatype = "port"
|
||||
d_port:depends({ type = "V2ray", protocol = "dokodemo-door" })
|
||||
d_port:depends({ type = "Xray", protocol = "dokodemo-door" })
|
||||
|
||||
decryption = s:option(Value, "decryption", translate("Encrypt Method"))
|
||||
decryption.default = "none"
|
||||
decryption:depends({ type = "V2ray", protocol = "vless" })
|
||||
decryption:depends({ type = "Xray", protocol = "vless" })
|
||||
|
||||
hysteria_protocol = s:option(ListValue, "hysteria_protocol", translate("Protocol"))
|
||||
hysteria_protocol:value("udp", "UDP")
|
||||
hysteria_protocol:value("faketcp", "faketcp")
|
||||
hysteria_protocol:value("wechat-video", "wechat-video")
|
||||
hysteria_protocol:depends("type", "Hysteria")
|
||||
function hysteria_protocol.cfgvalue(self, section)
|
||||
return m:get(section, "protocol")
|
||||
end
|
||||
function hysteria_protocol.write(self, section, value)
|
||||
m:set(section, "protocol", value)
|
||||
end
|
||||
|
||||
hysteria_obfs = s:option(Value, "hysteria_obfs", translate("Obfs Password"))
|
||||
hysteria_obfs:depends("type", "Hysteria")
|
||||
|
||||
hysteria_auth_type = s:option(ListValue, "hysteria_auth_type", translate("Auth Type"))
|
||||
hysteria_auth_type:value("disable", translate("Disable"))
|
||||
hysteria_auth_type:value("string", translate("STRING"))
|
||||
hysteria_auth_type:depends("type", "Hysteria")
|
||||
|
||||
hysteria_auth_password = s:option(Value, "hysteria_auth_password", translate("Auth Password"))
|
||||
hysteria_auth_password.password = true
|
||||
hysteria_auth_password:depends("hysteria_auth_type", "string")
|
||||
|
||||
hysteria_alpn = s:option(Value, "hysteria_alpn", translate("QUIC TLS ALPN"))
|
||||
hysteria_alpn:depends("type", "Hysteria")
|
||||
|
||||
hysteria_udp = s:option(Flag, "hysteria_udp", translate("UDP"))
|
||||
hysteria_udp.default = "1"
|
||||
hysteria_udp:depends("type", "Hysteria")
|
||||
|
||||
hysteria_up_mbps = s:option(Value, "hysteria_up_mbps", translate("Max upload Mbps"))
|
||||
hysteria_up_mbps.default = "10"
|
||||
hysteria_up_mbps:depends("type", "Hysteria")
|
||||
|
||||
hysteria_down_mbps = s:option(Value, "hysteria_down_mbps", translate("Max download Mbps"))
|
||||
hysteria_down_mbps.default = "50"
|
||||
hysteria_down_mbps:depends("type", "Hysteria")
|
||||
|
||||
hysteria_recv_window_conn = s:option(Value, "hysteria_recv_window_conn", translate("QUIC stream receive window"))
|
||||
hysteria_recv_window_conn:depends("type", "Hysteria")
|
||||
|
||||
hysteria_recv_window = s:option(Value, "hysteria_recv_window", translate("QUIC connection receive window"))
|
||||
hysteria_recv_window:depends("type", "Hysteria")
|
||||
|
||||
hysteria_disable_mtu_discovery = s:option(Flag, "hysteria_disable_mtu_discovery", translate("Disable MTU detection"))
|
||||
hysteria_disable_mtu_discovery:depends("type", "Hysteria")
|
||||
|
||||
ss_encrypt_method = s:option(ListValue, "ss_encrypt_method", translate("Encrypt Method"))
|
||||
for a, t in ipairs(ss_encrypt_method_list) do ss_encrypt_method:value(t) end
|
||||
ss_encrypt_method:depends("type", "SS")
|
||||
function ss_encrypt_method.cfgvalue(self, section)
|
||||
return m:get(section, "method")
|
||||
end
|
||||
function ss_encrypt_method.write(self, section, value)
|
||||
m:set(section, "method", value)
|
||||
end
|
||||
|
||||
ss_rust_encrypt_method = s:option(ListValue, "ss_rust_encrypt_method", translate("Encrypt Method"))
|
||||
for a, t in ipairs(ss_rust_encrypt_method_list) do ss_rust_encrypt_method:value(t) end
|
||||
ss_rust_encrypt_method:depends("type", "SS-Rust")
|
||||
function ss_rust_encrypt_method.cfgvalue(self, section)
|
||||
return m:get(section, "method")
|
||||
end
|
||||
function ss_rust_encrypt_method.write(self, section, value)
|
||||
m:set(section, "method", value)
|
||||
end
|
||||
|
||||
ssr_encrypt_method = s:option(ListValue, "ssr_encrypt_method", translate("Encrypt Method"))
|
||||
for a, t in ipairs(ssr_encrypt_method_list) do ssr_encrypt_method:value(t) end
|
||||
ssr_encrypt_method:depends("type", "SSR")
|
||||
function ssr_encrypt_method.cfgvalue(self, section)
|
||||
return m:get(section, "method")
|
||||
end
|
||||
function ssr_encrypt_method.write(self, section, value)
|
||||
m:set(section, "method", value)
|
||||
end
|
||||
|
||||
v_ss_encrypt_method = s:option(ListValue, "v_ss_encrypt_method", translate("Encrypt Method"))
|
||||
for a, t in ipairs(v_ss_encrypt_method_list) do v_ss_encrypt_method:value(t) end
|
||||
v_ss_encrypt_method:depends({ type = "V2ray", protocol = "shadowsocks" })
|
||||
function v_ss_encrypt_method.cfgvalue(self, section)
|
||||
return m:get(section, "method")
|
||||
end
|
||||
function v_ss_encrypt_method.write(self, section, value)
|
||||
m:set(section, "method", value)
|
||||
end
|
||||
|
||||
x_ss_encrypt_method = s:option(ListValue, "x_ss_encrypt_method", translate("Encrypt Method"))
|
||||
for a, t in ipairs(x_ss_encrypt_method_list) do x_ss_encrypt_method:value(t) end
|
||||
x_ss_encrypt_method:depends({ type = "Xray", protocol = "shadowsocks" })
|
||||
function x_ss_encrypt_method.cfgvalue(self, section)
|
||||
return m:get(section, "method")
|
||||
end
|
||||
function x_ss_encrypt_method.write(self, section, value)
|
||||
m:set(section, "method", value)
|
||||
end
|
||||
|
||||
iv_check = s:option(Flag, "iv_check", translate("IV Check"))
|
||||
iv_check:depends({ type = "V2ray", protocol = "shadowsocks" })
|
||||
iv_check:depends({ type = "Xray", protocol = "shadowsocks" })
|
||||
|
||||
ss_network = s:option(ListValue, "ss_network", translate("Transport"))
|
||||
ss_network.default = "tcp,udp"
|
||||
ss_network:value("tcp", "TCP")
|
||||
ss_network:value("udp", "UDP")
|
||||
ss_network:value("tcp,udp", "TCP,UDP")
|
||||
ss_network:depends({ type = "V2ray", protocol = "shadowsocks" })
|
||||
ss_network:depends({ type = "Xray", protocol = "shadowsocks" })
|
||||
|
||||
ssr_protocol = s:option(ListValue, "ssr_protocol", translate("Protocol"))
|
||||
for a, t in ipairs(ssr_protocol_list) do ssr_protocol:value(t) end
|
||||
ssr_protocol:depends("type", "SSR")
|
||||
function ssr_protocol.cfgvalue(self, section)
|
||||
return m:get(section, "protocol")
|
||||
end
|
||||
function ssr_protocol.write(self, section, value)
|
||||
m:set(section, "protocol", value)
|
||||
end
|
||||
|
||||
protocol_param = s:option(Value, "protocol_param", translate("Protocol_param"))
|
||||
protocol_param:depends("type", "SSR")
|
||||
|
||||
obfs = s:option(ListValue, "obfs", translate("Obfs"))
|
||||
for a, t in ipairs(ssr_obfs_list) do obfs:value(t) end
|
||||
obfs:depends("type", "SSR")
|
||||
|
||||
obfs_param = s:option(Value, "obfs_param", translate("Obfs_param"))
|
||||
obfs_param:depends("type", "SSR")
|
||||
|
||||
timeout = s:option(Value, "timeout", translate("Connection Timeout"))
|
||||
timeout.datatype = "uinteger"
|
||||
timeout.default = 300
|
||||
timeout:depends("type", "SS")
|
||||
timeout:depends("type", "SS-Rust")
|
||||
timeout:depends("type", "SSR")
|
||||
|
||||
udp_forward = s:option(Flag, "udp_forward", translate("UDP Forward"))
|
||||
udp_forward.default = "1"
|
||||
udp_forward.rmempty = false
|
||||
udp_forward:depends("type", "SSR")
|
||||
udp_forward:depends({ type = "V2ray", protocol = "socks" })
|
||||
udp_forward:depends({ type = "Xray", protocol = "socks" })
|
||||
|
||||
uuid = s:option(DynamicList, "uuid", translate("ID") .. "/" .. translate("Password"))
|
||||
for i = 1, 3 do
|
||||
uuid:value(api.gen_uuid(1))
|
||||
end
|
||||
uuid:depends({ type = "V2ray", protocol = "vmess" })
|
||||
uuid:depends({ type = "V2ray", protocol = "vless" })
|
||||
uuid:depends({ type = "V2ray", protocol = "trojan" })
|
||||
uuid:depends({ type = "Xray", protocol = "vmess" })
|
||||
uuid:depends({ type = "Xray", protocol = "vless" })
|
||||
uuid:depends({ type = "Xray", protocol = "trojan" })
|
||||
|
||||
tls = s:option(Flag, "tls", translate("TLS"))
|
||||
tls.default = 0
|
||||
tls.validate = function(self, value, t)
|
||||
if value then
|
||||
local type = type:formvalue(t) or ""
|
||||
if value == "1" then
|
||||
local ca = tls_certificateFile:formvalue(t) or ""
|
||||
local key = tls_keyFile:formvalue(t) or ""
|
||||
if ca == "" or key == "" then
|
||||
return nil, translate("Public key and Private key path can not be empty!")
|
||||
end
|
||||
end
|
||||
return value
|
||||
end
|
||||
end
|
||||
tls:depends({ type = "V2ray", protocol = "vmess" })
|
||||
tls:depends({ type = "V2ray", protocol = "vless" })
|
||||
tls:depends({ type = "V2ray", protocol = "socks" })
|
||||
tls:depends({ type = "V2ray", protocol = "shadowsocks" })
|
||||
tls:depends({ type = "V2ray", protocol = "trojan" })
|
||||
tls:depends({ type = "Xray", protocol = "vmess" })
|
||||
tls:depends({ type = "Xray", protocol = "vless" })
|
||||
tls:depends({ type = "Xray", protocol = "socks" })
|
||||
tls:depends({ type = "Xray", protocol = "shadowsocks" })
|
||||
tls:depends({ type = "Xray", protocol = "trojan" })
|
||||
|
||||
alpn = s:option(ListValue, "alpn", translate("alpn"))
|
||||
alpn.default = "h2,http/1.1"
|
||||
alpn:value("h2,http/1.1")
|
||||
alpn:value("h2")
|
||||
alpn:value("http/1.1")
|
||||
alpn:depends({ type = "V2ray", tls = true })
|
||||
alpn:depends({ type = "Xray", tls = true })
|
||||
|
||||
-- [[ TLS部分 ]] --
|
||||
|
||||
tls_certificateFile = s:option(FileUpload, "tls_certificateFile", translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem")
|
||||
tls_certificateFile.validate = function(self, value, t)
|
||||
if value and value ~= "" then
|
||||
if not nixio.fs.access(value) then
|
||||
return nil, translate("Can't find this file!")
|
||||
else
|
||||
return value
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
tls_certificateFile.default = "/etc/config/ssl/" .. arg[1] .. ".pem"
|
||||
tls_certificateFile:depends("tls", true)
|
||||
tls_certificateFile:depends("type", "Hysteria")
|
||||
|
||||
tls_keyFile = s:option(FileUpload, "tls_keyFile", translate("Private key absolute path"), translate("as:") .. "/etc/ssl/private.key")
|
||||
tls_keyFile.validate = function(self, value, t)
|
||||
if value and value ~= "" then
|
||||
if not nixio.fs.access(value) then
|
||||
return nil, translate("Can't find this file!")
|
||||
else
|
||||
return value
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
tls_keyFile.default = "/etc/config/ssl/" .. arg[1] .. ".key"
|
||||
tls_keyFile:depends("tls", true)
|
||||
tls_keyFile:depends("type", "Hysteria")
|
||||
|
||||
transport = s:option(ListValue, "transport", translate("Transport"))
|
||||
transport:value("tcp", "TCP")
|
||||
transport:value("mkcp", "mKCP")
|
||||
transport:value("ws", "WebSocket")
|
||||
transport:value("h2", "HTTP/2")
|
||||
transport:value("ds", "DomainSocket")
|
||||
transport:value("quic", "QUIC")
|
||||
transport:value("grpc", "gRPC")
|
||||
transport:depends({ type = "V2ray", protocol = "vmess" })
|
||||
transport:depends({ type = "V2ray", protocol = "vless" })
|
||||
transport:depends({ type = "V2ray", protocol = "socks" })
|
||||
transport:depends({ type = "V2ray", protocol = "shadowsocks" })
|
||||
transport:depends({ type = "V2ray", protocol = "trojan" })
|
||||
transport:depends({ type = "Xray", protocol = "vmess" })
|
||||
transport:depends({ type = "Xray", protocol = "vless" })
|
||||
transport:depends({ type = "Xray", protocol = "socks" })
|
||||
transport:depends({ type = "Xray", protocol = "shadowsocks" })
|
||||
transport:depends({ type = "Xray", protocol = "trojan" })
|
||||
|
||||
-- [[ WebSocket部分 ]]--
|
||||
|
||||
ws_host = s:option(Value, "ws_host", translate("WebSocket Host"))
|
||||
ws_host:depends("transport", "ws")
|
||||
ws_host:depends("ss_transport", "ws")
|
||||
|
||||
ws_path = s:option(Value, "ws_path", translate("WebSocket Path"))
|
||||
ws_path:depends("transport", "ws")
|
||||
ws_path:depends("ss_transport", "ws")
|
||||
ws_path:depends({ type = "Brook", brook_protocol = "wsserver" })
|
||||
|
||||
-- [[ HTTP/2部分 ]]--
|
||||
|
||||
h2_host = s:option(Value, "h2_host", translate("HTTP/2 Host"))
|
||||
h2_host:depends("transport", "h2")
|
||||
h2_host:depends("ss_transport", "h2")
|
||||
|
||||
h2_path = s:option(Value, "h2_path", translate("HTTP/2 Path"))
|
||||
h2_path:depends("transport", "h2")
|
||||
h2_path:depends("ss_transport", "h2")
|
||||
|
||||
-- [[ TCP部分 ]]--
|
||||
|
||||
-- TCP伪装
|
||||
tcp_guise = s:option(ListValue, "tcp_guise", translate("Camouflage Type"))
|
||||
tcp_guise:value("none", "none")
|
||||
tcp_guise:value("http", "http")
|
||||
tcp_guise:depends("transport", "tcp")
|
||||
|
||||
-- HTTP域名
|
||||
tcp_guise_http_host = s:option(DynamicList, "tcp_guise_http_host", translate("HTTP Host"))
|
||||
tcp_guise_http_host:depends("tcp_guise", "http")
|
||||
|
||||
-- HTTP路径
|
||||
tcp_guise_http_path = s:option(DynamicList, "tcp_guise_http_path", translate("HTTP Path"))
|
||||
tcp_guise_http_path:depends("tcp_guise", "http")
|
||||
|
||||
-- [[ mKCP部分 ]]--
|
||||
|
||||
mkcp_guise = s:option(ListValue, "mkcp_guise", translate("Camouflage Type"), translate('<br />none: default, no masquerade, data sent is packets with no characteristics.<br />srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).<br />utp: packets disguised as uTP will be recognized as bittorrent downloaded data.<br />wechat-video: packets disguised as WeChat video calls.<br />dtls: disguised as DTLS 1.2 packet.<br />wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)'))
|
||||
for a, t in ipairs(header_type_list) do mkcp_guise:value(t) end
|
||||
mkcp_guise:depends("transport", "mkcp")
|
||||
|
||||
mkcp_mtu = s:option(Value, "mkcp_mtu", translate("KCP MTU"))
|
||||
mkcp_mtu.default = "1350"
|
||||
mkcp_mtu:depends("transport", "mkcp")
|
||||
|
||||
mkcp_tti = s:option(Value, "mkcp_tti", translate("KCP TTI"))
|
||||
mkcp_tti.default = "20"
|
||||
mkcp_tti:depends("transport", "mkcp")
|
||||
|
||||
mkcp_uplinkCapacity = s:option(Value, "mkcp_uplinkCapacity", translate("KCP uplinkCapacity"))
|
||||
mkcp_uplinkCapacity.default = "5"
|
||||
mkcp_uplinkCapacity:depends("transport", "mkcp")
|
||||
|
||||
mkcp_downlinkCapacity = s:option(Value, "mkcp_downlinkCapacity", translate("KCP downlinkCapacity"))
|
||||
mkcp_downlinkCapacity.default = "20"
|
||||
mkcp_downlinkCapacity:depends("transport", "mkcp")
|
||||
|
||||
mkcp_congestion = s:option(Flag, "mkcp_congestion", translate("KCP Congestion"))
|
||||
mkcp_congestion:depends("transport", "mkcp")
|
||||
|
||||
mkcp_readBufferSize = s:option(Value, "mkcp_readBufferSize", translate("KCP readBufferSize"))
|
||||
mkcp_readBufferSize.default = "1"
|
||||
mkcp_readBufferSize:depends("transport", "mkcp")
|
||||
|
||||
mkcp_writeBufferSize = s:option(Value, "mkcp_writeBufferSize", translate("KCP writeBufferSize"))
|
||||
mkcp_writeBufferSize.default = "1"
|
||||
mkcp_writeBufferSize:depends("transport", "mkcp")
|
||||
|
||||
mkcp_seed = s:option(Value, "mkcp_seed", translate("KCP Seed"))
|
||||
mkcp_seed:depends("transport", "mkcp")
|
||||
|
||||
-- [[ DomainSocket部分 ]]--
|
||||
|
||||
ds_path = s:option(Value, "ds_path", "Path", translate("A legal file path. This file must not exist before running."))
|
||||
ds_path:depends("transport", "ds")
|
||||
|
||||
-- [[ QUIC部分 ]]--
|
||||
quic_security = s:option(ListValue, "quic_security", translate("Encrypt Method"))
|
||||
quic_security:value("none")
|
||||
quic_security:value("aes-128-gcm")
|
||||
quic_security:value("chacha20-poly1305")
|
||||
quic_security:depends("transport", "quic")
|
||||
|
||||
quic_key = s:option(Value, "quic_key", translate("Encrypt Method") .. translate("Key"))
|
||||
quic_key:depends("transport", "quic")
|
||||
|
||||
quic_guise = s:option(ListValue, "quic_guise", translate("Camouflage Type"))
|
||||
for a, t in ipairs(header_type_list) do quic_guise:value(t) end
|
||||
quic_guise:depends("transport", "quic")
|
||||
|
||||
-- [[ gRPC部分 ]]--
|
||||
grpc_serviceName = s:option(Value, "grpc_serviceName", "ServiceName")
|
||||
grpc_serviceName:depends("transport", "grpc")
|
||||
|
||||
acceptProxyProtocol = s:option(Flag, "acceptProxyProtocol", translate("acceptProxyProtocol"), translate("Whether to receive PROXY protocol, when this node want to be fallback or forwarded by proxy, it must be enable, otherwise it cannot be used."))
|
||||
acceptProxyProtocol:depends({ type = "V2ray", transport = "tcp" })
|
||||
acceptProxyProtocol:depends({ type = "V2ray", transport = "ws" })
|
||||
acceptProxyProtocol:depends({ type = "Xray", transport = "tcp" })
|
||||
acceptProxyProtocol:depends({ type = "Xray", transport = "ws" })
|
||||
|
||||
-- [[ Fallback部分 ]]--
|
||||
fallback = s:option(Flag, "fallback", translate("Fallback"))
|
||||
fallback:depends({ type = "V2ray", protocol = "vless", transport = "tcp" })
|
||||
fallback:depends({ type = "V2ray", protocol = "trojan", transport = "tcp" })
|
||||
fallback:depends({ type = "Xray", protocol = "vless", transport = "tcp" })
|
||||
fallback:depends({ type = "Xray", protocol = "trojan", transport = "tcp" })
|
||||
|
||||
--[[
|
||||
fallback_alpn = s:option(Value, "fallback_alpn", "Fallback alpn")
|
||||
fallback_alpn:depends("fallback", true)
|
||||
|
||||
fallback_path = s:option(Value, "fallback_path", "Fallback path")
|
||||
fallback_path:depends("fallback", true)
|
||||
|
||||
fallback_dest = s:option(Value, "fallback_dest", "Fallback dest")
|
||||
fallback_dest:depends("fallback", true)
|
||||
|
||||
fallback_xver = s:option(Value, "fallback_xver", "Fallback xver")
|
||||
fallback_xver.default = 0
|
||||
fallback_xver:depends("fallback", true)
|
||||
]]--
|
||||
|
||||
fallback_list = s:option(DynamicList, "fallback_list", "Fallback", translate("dest,path"))
|
||||
fallback_list:depends("fallback", true)
|
||||
|
||||
tcp_fast_open = s:option(Flag, "tcp_fast_open", translate("TCP Fast Open"))
|
||||
tcp_fast_open.default = "0"
|
||||
tcp_fast_open:depends("type", "SS")
|
||||
tcp_fast_open:depends("type", "SS-Rust")
|
||||
tcp_fast_open:depends("type", "SSR")
|
||||
|
||||
remote_address = s:option(Value, "remote_address", translate("Remote Address"))
|
||||
remote_address.default = "127.0.0.1"
|
||||
remote_address:depends("remote_enable", 1)
|
||||
|
||||
remote_port = s:option(Value, "remote_port", translate("Remote Port"))
|
||||
remote_port.datatype = "port"
|
||||
remote_port.default = "80"
|
||||
remote_port:depends("remote_enable", 1)
|
||||
|
||||
bind_local = s:option(Flag, "bind_local", translate("Bind Local"), translate("When selected, it can only be accessed locally, It is recommended to turn on when using reverse proxies or be fallback."))
|
||||
bind_local.default = "0"
|
||||
bind_local:depends("type", "V2ray")
|
||||
bind_local:depends("type", "Xray")
|
||||
|
||||
accept_lan = s:option(Flag, "accept_lan", translate("Accept LAN Access"), translate("When selected, it can accessed lan , this will not be safe!"))
|
||||
accept_lan.default = "0"
|
||||
accept_lan:depends("type", "V2ray")
|
||||
accept_lan:depends("type", "Xray")
|
||||
|
||||
local nodes_table = {}
|
||||
for k, e in ipairs(api.get_valid_nodes()) do
|
||||
if e.node_type == "normal" and (e.type == "V2ray" or e.type == "Xray") then
|
||||
nodes_table[#nodes_table + 1] = {
|
||||
id = e[".name"],
|
||||
remarks = e["remark"]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
outbound_node = s:option(ListValue, "outbound_node", translate("outbound node"))
|
||||
outbound_node:value("nil", translate("Close"))
|
||||
outbound_node:value("_socks", translate("Custom Socks"))
|
||||
outbound_node:value("_http", translate("Custom HTTP"))
|
||||
outbound_node:value("_iface", translate("Custom Interface") .. " (Only Support Xray)")
|
||||
for k, v in pairs(nodes_table) do outbound_node:value(v.id, v.remarks) end
|
||||
outbound_node.default = "nil"
|
||||
outbound_node:depends("type", "V2ray")
|
||||
outbound_node:depends("type", "Xray")
|
||||
|
||||
outbound_node_address = s:option(Value, "outbound_node_address", translate("Address (Support Domain Name)"))
|
||||
outbound_node_address:depends("outbound_node", "_socks")
|
||||
outbound_node_address:depends("outbound_node", "_http")
|
||||
|
||||
outbound_node_port = s:option(Value, "outbound_node_port", translate("Port"))
|
||||
outbound_node_port.datatype = "port"
|
||||
outbound_node_port:depends("outbound_node", "_socks")
|
||||
outbound_node_port:depends("outbound_node", "_http")
|
||||
|
||||
outbound_node_username = s:option(Value, "outbound_node_username", translate("Username"))
|
||||
outbound_node_username:depends("outbound_node", "_socks")
|
||||
outbound_node_username:depends("outbound_node", "_http")
|
||||
|
||||
outbound_node_password = s:option(Value, "outbound_node_password", translate("Password"))
|
||||
outbound_node_password.password = true
|
||||
outbound_node_password:depends("outbound_node", "_socks")
|
||||
outbound_node_password:depends("outbound_node", "_http")
|
||||
|
||||
outbound_node_iface = s:option(Value, "outbound_node_iface", translate("Interface"))
|
||||
outbound_node_iface.default = "eth1"
|
||||
outbound_node_iface:depends("outbound_node", "_iface")
|
||||
|
||||
log = s:option(Flag, "log", translate("Log"))
|
||||
log.default = "1"
|
||||
log.rmempty = false
|
||||
|
||||
loglevel = s:option(ListValue, "loglevel", translate("Log Level"))
|
||||
loglevel.default = "warning"
|
||||
loglevel:value("debug")
|
||||
loglevel:value("info")
|
||||
loglevel:value("warning")
|
||||
loglevel:value("error")
|
||||
loglevel:depends({ type = "V2ray", log = true })
|
||||
loglevel:depends({ type = "Xray", log = true })
|
||||
|
||||
return m
|
881
luci-app-passwall2/luasrc/passwall2/api.lua
Normal file
881
luci-app-passwall2/luasrc/passwall2/api.lua
Normal file
@ -0,0 +1,881 @@
|
||||
module("luci.passwall2.api", package.seeall)
|
||||
local com = require "luci.passwall2.com"
|
||||
bin = require "nixio".bin
|
||||
fs = require "nixio.fs"
|
||||
sys = require "luci.sys"
|
||||
uci = require"luci.model.uci".cursor()
|
||||
util = require "luci.util"
|
||||
datatypes = require "luci.cbi.datatypes"
|
||||
jsonc = require "luci.jsonc"
|
||||
i18n = require "luci.i18n"
|
||||
|
||||
appname = "passwall2"
|
||||
curl_args = {"-skfL", "--connect-timeout 3", "--retry 3", "-m 60"}
|
||||
command_timeout = 300
|
||||
OPENWRT_ARCH = nil
|
||||
DISTRIB_ARCH = nil
|
||||
|
||||
LOG_FILE = "/tmp/log/passwall2.log"
|
||||
|
||||
function log(...)
|
||||
local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ")
|
||||
local f, err = io.open(LOG_FILE, "a")
|
||||
if f and err == nil then
|
||||
f:write(result .. "\n")
|
||||
f:close()
|
||||
end
|
||||
end
|
||||
|
||||
function exec_call(cmd)
|
||||
local process = io.popen(cmd .. '; echo -e "\n$?"')
|
||||
local lines = {}
|
||||
local result = ""
|
||||
local return_code
|
||||
for line in process:lines() do
|
||||
lines[#lines + 1] = line
|
||||
end
|
||||
process:close()
|
||||
if #lines > 0 then
|
||||
return_code = lines[#lines]
|
||||
for i = 1, #lines - 1 do
|
||||
result = result .. lines[i] .. ((i == #lines - 1) and "" or "\n")
|
||||
end
|
||||
end
|
||||
return tonumber(return_code), trim(result)
|
||||
end
|
||||
|
||||
function base64Decode(text)
|
||||
local raw = text
|
||||
if not text then return '' end
|
||||
text = text:gsub("%z", "")
|
||||
text = text:gsub("%c", "")
|
||||
text = text:gsub("_", "/")
|
||||
text = text:gsub("-", "+")
|
||||
local mod4 = #text % 4
|
||||
text = text .. string.sub('====', mod4 + 1)
|
||||
local result = nixio.bin.b64decode(text)
|
||||
if result then
|
||||
return result:gsub("%z", "")
|
||||
else
|
||||
return raw
|
||||
end
|
||||
end
|
||||
|
||||
function curl_base(url, file, args)
|
||||
if not args then args = {} end
|
||||
if file then
|
||||
args[#args + 1] = "-o " .. file
|
||||
end
|
||||
local cmd = string.format('curl %s "%s"', table_join(args), url)
|
||||
return exec_call(cmd)
|
||||
end
|
||||
|
||||
function curl_proxy(url, file, args)
|
||||
--使用代理
|
||||
local socks_server = luci.sys.exec("[ -f /tmp/etc/passwall2/global_SOCKS_server ] && echo -n $(cat /tmp/etc/passwall2/global_SOCKS_server) || echo -n ''")
|
||||
if socks_server ~= "" then
|
||||
if not args then args = {} end
|
||||
local tmp_args = clone(args)
|
||||
tmp_args[#tmp_args + 1] = "-x socks5h://" .. socks_server
|
||||
return curl_base(url, file, tmp_args)
|
||||
end
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
function curl_logic(url, file, args)
|
||||
local return_code, result = curl_proxy(url, file, args)
|
||||
if not return_code or return_code ~= 0 then
|
||||
return_code, result = curl_base(url, file, args)
|
||||
end
|
||||
return return_code, result
|
||||
end
|
||||
|
||||
function url(...)
|
||||
local url = string.format("admin/services/%s", appname)
|
||||
local args = { ... }
|
||||
for i, v in pairs(args) do
|
||||
if v ~= "" then
|
||||
url = url .. "/" .. v
|
||||
end
|
||||
end
|
||||
return require "luci.dispatcher".build_url(url)
|
||||
end
|
||||
|
||||
function trim(s)
|
||||
return (s:gsub("^%s*(.-)%s*$", "%1"))
|
||||
end
|
||||
|
||||
function is_exist(table, value)
|
||||
for index, k in ipairs(table) do
|
||||
if k == value then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function repeat_exist(table, value)
|
||||
local count = 0
|
||||
for index, k in ipairs(table) do
|
||||
if k:find("-") and k == value then
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
if count > 1 then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function get_args(arg)
|
||||
local var = {}
|
||||
for i, arg_k in pairs(arg) do
|
||||
if i > 0 then
|
||||
local v = arg[i + 1]
|
||||
if v then
|
||||
if repeat_exist(arg, v) == false then
|
||||
var[arg_k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return var
|
||||
end
|
||||
|
||||
function get_function_args(arg)
|
||||
local var = nil
|
||||
if arg and #arg > 1 then
|
||||
local param = {}
|
||||
for i = 2, #arg do
|
||||
param[#param + 1] = arg[i]
|
||||
end
|
||||
var = get_args(param)
|
||||
end
|
||||
return var
|
||||
end
|
||||
|
||||
function strToTable(str)
|
||||
if str == nil or type(str) ~= "string" then
|
||||
return {}
|
||||
end
|
||||
|
||||
return loadstring("return " .. str)()
|
||||
end
|
||||
|
||||
function is_normal_node(e)
|
||||
if e and e.type and e.protocol and (e.protocol == "_balancing" or e.protocol == "_shunt" or e.protocol == "_iface") then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function is_special_node(e)
|
||||
return is_normal_node(e) == false
|
||||
end
|
||||
|
||||
function is_ip(val)
|
||||
if is_ipv6(val) then
|
||||
val = get_ipv6_only(val)
|
||||
end
|
||||
return datatypes.ipaddr(val)
|
||||
end
|
||||
|
||||
function is_ipv6(val)
|
||||
local str = val
|
||||
local address = val:match('%[(.*)%]')
|
||||
if address then
|
||||
str = address
|
||||
end
|
||||
if datatypes.ip6addr(str) then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function is_ipv6addrport(val)
|
||||
if is_ipv6(val) then
|
||||
local address, port = val:match('%[(.*)%]:([^:]+)$')
|
||||
if port then
|
||||
return datatypes.port(port)
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function get_ipv6_only(val)
|
||||
local result = ""
|
||||
if is_ipv6(val) then
|
||||
result = val
|
||||
if val:match('%[(.*)%]') then
|
||||
result = val:match('%[(.*)%]')
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function get_ipv6_full(val)
|
||||
local result = ""
|
||||
if is_ipv6(val) then
|
||||
result = val
|
||||
if not val:match('%[(.*)%]') then
|
||||
result = "[" .. result .. "]"
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function get_ip_type(val)
|
||||
if is_ipv6(val) then
|
||||
return "6"
|
||||
elseif datatypes.ip4addr(val) then
|
||||
return "4"
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
function is_mac(val)
|
||||
return datatypes.macaddr(val)
|
||||
end
|
||||
|
||||
function ip_or_mac(val)
|
||||
if val then
|
||||
if get_ip_type(val) == "4" then
|
||||
return "ip"
|
||||
end
|
||||
if is_mac(val) then
|
||||
return "mac"
|
||||
end
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
function iprange(val)
|
||||
if val then
|
||||
local ipStart, ipEnd = val:match("^([^/]+)-([^/]+)$")
|
||||
if (ipStart and datatypes.ip4addr(ipStart)) and (ipEnd and datatypes.ip4addr(ipEnd)) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function get_domain_from_url(url)
|
||||
local domain = string.match(url, "//([^/]+)")
|
||||
if domain then
|
||||
return domain
|
||||
end
|
||||
return url
|
||||
end
|
||||
|
||||
function get_valid_nodes()
|
||||
local nodes_ping = uci_get_type("global_other", "nodes_ping") or ""
|
||||
local nodes = {}
|
||||
uci:foreach(appname, "nodes", function(e)
|
||||
e.id = e[".name"]
|
||||
if e.type and e.remarks then
|
||||
if e.protocol and (e.protocol == "_balancing" or e.protocol == "_shunt" or e.protocol == "_iface") then
|
||||
e["remark"] = "%s:[%s] " % {i18n.translatef(e.type .. e.protocol), e.remarks}
|
||||
e["node_type"] = "special"
|
||||
nodes[#nodes + 1] = e
|
||||
end
|
||||
if e.port and e.address then
|
||||
local address = e.address
|
||||
if is_ip(address) or datatypes.hostname(address) then
|
||||
local type = e.type
|
||||
if (type == "V2ray" or type == "Xray") and e.protocol then
|
||||
local protocol = e.protocol
|
||||
if protocol == "vmess" then
|
||||
protocol = "VMess"
|
||||
elseif protocol == "vless" then
|
||||
protocol = "VLESS"
|
||||
else
|
||||
protocol = protocol:gsub("^%l",string.upper)
|
||||
end
|
||||
type = type .. " " .. protocol
|
||||
end
|
||||
if is_ipv6(address) then address = get_ipv6_full(address) end
|
||||
e["remark"] = "%s:[%s]" % {type, e.remarks}
|
||||
if nodes_ping:find("info") then
|
||||
e["remark"] = "%s:[%s] %s:%s" % {type, e.remarks, address, e.port}
|
||||
end
|
||||
e.node_type = "normal"
|
||||
nodes[#nodes + 1] = e
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
return nodes
|
||||
end
|
||||
|
||||
function get_node_remarks(n)
|
||||
local remarks = ""
|
||||
if n then
|
||||
if n.protocol and (n.protocol == "_balancing" or n.protocol == "_shunt" or n.protocol == "_iface") then
|
||||
remarks = "%s:[%s] " % {i18n.translatef(n.type .. n.protocol), n.remarks}
|
||||
else
|
||||
local type2 = n.type
|
||||
if (n.type == "V2ray" or n.type == "Xray") and n.protocol then
|
||||
local protocol = n.protocol
|
||||
if protocol == "vmess" then
|
||||
protocol = "VMess"
|
||||
elseif protocol == "vless" then
|
||||
protocol = "VLESS"
|
||||
else
|
||||
protocol = protocol:gsub("^%l",string.upper)
|
||||
end
|
||||
type2 = type2 .. " " .. protocol
|
||||
end
|
||||
remarks = "%s:[%s]" % {type2, n.remarks}
|
||||
end
|
||||
end
|
||||
return remarks
|
||||
end
|
||||
|
||||
function get_full_node_remarks(n)
|
||||
local remarks = get_node_remarks(n)
|
||||
if #remarks > 0 then
|
||||
if n.address and n.port then
|
||||
remarks = remarks .. " " .. n.address .. ":" .. n.port
|
||||
end
|
||||
end
|
||||
return remarks
|
||||
end
|
||||
|
||||
function gen_uuid(format)
|
||||
local uuid = sys.exec("echo -n $(cat /proc/sys/kernel/random/uuid)")
|
||||
if format == nil then
|
||||
uuid = string.gsub(uuid, "-", "")
|
||||
end
|
||||
return uuid
|
||||
end
|
||||
|
||||
function gen_short_uuid()
|
||||
return sys.exec("echo -n $(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 8)")
|
||||
end
|
||||
|
||||
function uci_get_type(type, config, default)
|
||||
local value = uci:get_first(appname, type, config, default) or sys.exec("echo -n $(uci -q get " .. appname .. ".@" .. type .."[0]." .. config .. ")")
|
||||
if (value == nil or value == "") and (default and default ~= "") then
|
||||
value = default
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
function uci_get_type_id(id, config, default)
|
||||
local value = uci:get(appname, id, config, default) or sys.exec("echo -n $(uci -q get " .. appname .. "." .. id .. "." .. config .. ")")
|
||||
if (value == nil or value == "") and (default and default ~= "") then
|
||||
value = default
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
function chmod_755(file)
|
||||
if file and file ~= "" then
|
||||
if not fs.access(file, "rwx", "rx", "rx") then
|
||||
fs.chmod(file, 755)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function get_customed_path(e)
|
||||
return uci_get_type("global_app", e .. "_file")
|
||||
end
|
||||
|
||||
function is_finded(e)
|
||||
return luci.sys.exec('type -t -p "/bin/%s" -p "/usr/bin/%s" -p "%s" "%s"' % {e, e, get_customed_path(e), e}) ~= "" and true or false
|
||||
end
|
||||
|
||||
function clone(org)
|
||||
local function copy(org, res)
|
||||
for k,v in pairs(org) do
|
||||
if type(v) ~= "table" then
|
||||
res[k] = v;
|
||||
else
|
||||
res[k] = {};
|
||||
copy(v, res[k])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local res = {}
|
||||
copy(org, res)
|
||||
return res
|
||||
end
|
||||
|
||||
local function get_bin_version_cache(file, cmd)
|
||||
sys.call("mkdir -p /tmp/etc/passwall2_tmp")
|
||||
if fs.access(file) then
|
||||
chmod_755(file)
|
||||
local md5 = sys.exec("echo -n $(md5sum " .. file .. " | awk '{print $1}')")
|
||||
if fs.access("/tmp/etc/passwall2_tmp/" .. md5) then
|
||||
return sys.exec("echo -n $(cat /tmp/etc/passwall2_tmp/%s)" % md5)
|
||||
else
|
||||
local version = sys.exec(string.format("echo -n $(%s %s)", file, cmd))
|
||||
if version and version ~= "" then
|
||||
sys.call("echo '" .. version .. "' > " .. "/tmp/etc/passwall2_tmp/" .. md5)
|
||||
return version
|
||||
end
|
||||
end
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
function get_app_path(app_name)
|
||||
local def_path = com[app_name].default_path
|
||||
local path = uci_get_type("global_app", app_name:gsub("%-","_") .. "_file")
|
||||
path = path and (#path>0 and path or def_path) or def_path
|
||||
return path
|
||||
end
|
||||
|
||||
function get_app_version(app_name, file)
|
||||
if file == nil then file = get_app_path(app_name) end
|
||||
return get_bin_version_cache(file, com[app_name].cmd_version)
|
||||
end
|
||||
|
||||
function is_file(path)
|
||||
if path and #path > 1 then
|
||||
if sys.exec('[ -f "%s" ] && echo -n 1' % path) == "1" then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function is_dir(path)
|
||||
if path and #path > 1 then
|
||||
if sys.exec('[ -d "%s" ] && echo -n 1' % path) == "1" then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function get_final_dir(path)
|
||||
if is_dir(path) then
|
||||
return path
|
||||
else
|
||||
return get_final_dir(fs.dirname(path))
|
||||
end
|
||||
end
|
||||
|
||||
function get_free_space(dir)
|
||||
if dir == nil then dir = "/" end
|
||||
if sys.call("df -k " .. dir .. " >/dev/null 2>&1") == 0 then
|
||||
return tonumber(sys.exec("echo -n $(df -k " .. dir .. " | awk 'NR>1' | awk '{print $4}')"))
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function get_file_space(file)
|
||||
if file == nil then return 0 end
|
||||
if fs.access(file) then
|
||||
return tonumber(sys.exec("echo -n $(du -k " .. file .. " | awk '{print $1}')"))
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
function _unpack(t, i)
|
||||
i = i or 1
|
||||
if t[i] ~= nil then return t[i], _unpack(t, i + 1) end
|
||||
end
|
||||
|
||||
function table_join(t, s)
|
||||
if not s then
|
||||
s = " "
|
||||
end
|
||||
local str = ""
|
||||
for index, value in ipairs(t) do
|
||||
str = str .. t[index] .. (index == #t and "" or s)
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
function exec(cmd, args, writer, timeout)
|
||||
local os = require "os"
|
||||
local nixio = require "nixio"
|
||||
|
||||
local fdi, fdo = nixio.pipe()
|
||||
local pid = nixio.fork()
|
||||
|
||||
if pid > 0 then
|
||||
fdo:close()
|
||||
|
||||
if writer or timeout then
|
||||
local starttime = os.time()
|
||||
while true do
|
||||
if timeout and os.difftime(os.time(), starttime) >= timeout then
|
||||
nixio.kill(pid, nixio.const.SIGTERM)
|
||||
return 1
|
||||
end
|
||||
|
||||
if writer then
|
||||
local buffer = fdi:read(2048)
|
||||
if buffer and #buffer > 0 then
|
||||
writer(buffer)
|
||||
end
|
||||
end
|
||||
|
||||
local wpid, stat, code = nixio.waitpid(pid, "nohang")
|
||||
|
||||
if wpid and stat == "exited" then return code end
|
||||
|
||||
if not writer and timeout then nixio.nanosleep(1) end
|
||||
end
|
||||
else
|
||||
local wpid, stat, code = nixio.waitpid(pid)
|
||||
return wpid and stat == "exited" and code
|
||||
end
|
||||
elseif pid == 0 then
|
||||
nixio.dup(fdo, nixio.stdout)
|
||||
fdi:close()
|
||||
fdo:close()
|
||||
nixio.exece(cmd, args, nil)
|
||||
nixio.stdout:close()
|
||||
os.exit(1)
|
||||
end
|
||||
end
|
||||
|
||||
function parseURL(url)
|
||||
if not url or url == "" then
|
||||
return nil
|
||||
end
|
||||
local pattern = "^(%w+)://"
|
||||
local protocol = url:match(pattern)
|
||||
|
||||
if not protocol then
|
||||
--error("Invalid URL: " .. url)
|
||||
return nil
|
||||
end
|
||||
|
||||
local auth_host_port = url:sub(#protocol + 4)
|
||||
local auth_pattern = "^([^@]+)@"
|
||||
local auth = auth_host_port:match(auth_pattern)
|
||||
local username, password
|
||||
|
||||
if auth then
|
||||
username, password = auth:match("^([^:]+):([^:]+)$")
|
||||
auth_host_port = auth_host_port:sub(#auth + 2)
|
||||
end
|
||||
|
||||
local host, port = auth_host_port:match("^([^:]+):(%d+)$")
|
||||
|
||||
if not host or not port then
|
||||
--error("Invalid URL: " .. url)
|
||||
return nil
|
||||
end
|
||||
|
||||
return {
|
||||
protocol = protocol,
|
||||
username = username,
|
||||
password = password,
|
||||
host = host,
|
||||
port = tonumber(port)
|
||||
}
|
||||
end
|
||||
|
||||
function compare_versions(ver1, comp, ver2)
|
||||
local table = table
|
||||
|
||||
if not ver1 then ver1 = "" end
|
||||
if not ver2 then ver2 = "" end
|
||||
|
||||
local av1 = util.split(ver1, "[%.%-]", nil, true)
|
||||
local av2 = util.split(ver2, "[%.%-]", nil, true)
|
||||
|
||||
local max = table.getn(av1)
|
||||
local n2 = table.getn(av2)
|
||||
if (max < n2) then max = n2 end
|
||||
|
||||
for i = 1, max, 1 do
|
||||
local s1 = tonumber(av1[i] or 0) or 0
|
||||
local s2 = tonumber(av2[i] or 0) or 0
|
||||
|
||||
if comp == "~=" and (s1 ~= s2) then return true end
|
||||
if (comp == "<" or comp == "<=") and (s1 < s2) then return true end
|
||||
if (comp == ">" or comp == ">=") and (s1 > s2) then return true end
|
||||
if (s1 ~= s2) then return false end
|
||||
end
|
||||
|
||||
return not (comp == "<" or comp == ">")
|
||||
end
|
||||
|
||||
local function auto_get_arch()
|
||||
local arch = nixio.uname().machine or ""
|
||||
if not OPENWRT_ARCH and fs.access("/usr/lib/os-release") then
|
||||
OPENWRT_ARCH = sys.exec("echo -n $(grep 'OPENWRT_ARCH' /usr/lib/os-release | awk -F '[\\042\\047]' '{print $2}')")
|
||||
if OPENWRT_ARCH == "" then OPENWRT_ARCH = nil end
|
||||
end
|
||||
if not DISTRIB_ARCH and fs.access("/etc/openwrt_release") then
|
||||
DISTRIB_ARCH = sys.exec("echo -n $(grep 'DISTRIB_ARCH' /etc/openwrt_release | awk -F '[\\042\\047]' '{print $2}')")
|
||||
if DISTRIB_ARCH == "" then DISTRIB_ARCH = nil end
|
||||
end
|
||||
|
||||
if arch:match("^i[%d]86$") then
|
||||
arch = "x86"
|
||||
elseif arch:match("armv5") then -- armv5l
|
||||
arch = "armv5"
|
||||
elseif arch:match("armv6") then
|
||||
arch = "armv6"
|
||||
elseif arch:match("armv7") then -- armv7l
|
||||
arch = "armv7"
|
||||
end
|
||||
|
||||
if OPENWRT_ARCH or DISTRIB_ARCH then
|
||||
if arch == "mips" then
|
||||
if OPENWRT_ARCH and OPENWRT_ARCH:match("mipsel") == "mipsel"
|
||||
or DISTRIB_ARCH and DISTRIB_ARCH:match("mipsel") == "mipsel" then
|
||||
arch = "mipsel"
|
||||
end
|
||||
elseif arch == "armv7" then
|
||||
if OPENWRT_ARCH and not OPENWRT_ARCH:match("vfp") and not OPENWRT_ARCH:match("neon")
|
||||
or DISTRIB_ARCH and not DISTRIB_ARCH:match("vfp") and not DISTRIB_ARCH:match("neon") then
|
||||
arch = "armv5"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return util.trim(arch)
|
||||
end
|
||||
|
||||
local default_file_tree = {
|
||||
x86_64 = "amd64",
|
||||
x86 = "386",
|
||||
aarch64 = "arm64",
|
||||
mips = "mips",
|
||||
mipsel = "mipsle",
|
||||
armv5 = "arm.*5",
|
||||
armv6 = "arm.*6[^4]*",
|
||||
armv7 = "arm.*7",
|
||||
armv8 = "arm64"
|
||||
}
|
||||
|
||||
function get_api_json(url)
|
||||
local jsonc = require "luci.jsonc"
|
||||
local return_code, content = curl_logic(url, nil, curl_args)
|
||||
if return_code ~= 0 or content == "" then return {} end
|
||||
return jsonc.parse(content) or {}
|
||||
end
|
||||
|
||||
local function check_path(app_name)
|
||||
local path = get_app_path(app_name) or ""
|
||||
if path == "" then
|
||||
return {
|
||||
code = 1,
|
||||
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", app_name)
|
||||
}
|
||||
end
|
||||
return {
|
||||
code = 0,
|
||||
app_path = path
|
||||
}
|
||||
end
|
||||
|
||||
function to_check(arch, app_name)
|
||||
local result = check_path(app_name)
|
||||
if result.code ~= 0 then
|
||||
return result
|
||||
end
|
||||
|
||||
if not arch or arch == "" then arch = auto_get_arch() end
|
||||
|
||||
local file_tree = com[app_name].file_tree[arch] or default_file_tree[arch] or ""
|
||||
|
||||
if file_tree == "" then
|
||||
return {
|
||||
code = 1,
|
||||
error = i18n.translate("Can't determine ARCH, or ARCH not supported.")
|
||||
}
|
||||
end
|
||||
|
||||
local local_version = get_app_version(app_name)
|
||||
local match_file_name = string.format(com[app_name].match_fmt_str, file_tree)
|
||||
local json = get_api_json(com[app_name]:get_url())
|
||||
|
||||
if #json > 0 then
|
||||
json = json[1]
|
||||
end
|
||||
|
||||
if json.tag_name == nil then
|
||||
return {
|
||||
code = 1,
|
||||
error = i18n.translate("Get remote version info failed.")
|
||||
}
|
||||
end
|
||||
|
||||
local remote_version = json.tag_name
|
||||
local has_update = compare_versions(local_version:match("[^v]+"), "<", remote_version:match("[^v]+"))
|
||||
|
||||
if not has_update then
|
||||
return {
|
||||
code = 0,
|
||||
local_version = local_version,
|
||||
remote_version = remote_version
|
||||
}
|
||||
end
|
||||
|
||||
local asset = {}
|
||||
for _, v in ipairs(json.assets) do
|
||||
if v.name and v.name:match(match_file_name) then
|
||||
asset = v
|
||||
break
|
||||
end
|
||||
end
|
||||
if not asset.browser_download_url then
|
||||
return {
|
||||
code = 1,
|
||||
local_version = local_version,
|
||||
remote_version = remote_version,
|
||||
html_url = json.html_url,
|
||||
data = asset,
|
||||
error = i18n.translate("New version found, but failed to get new version download url.")
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
code = 0,
|
||||
has_update = true,
|
||||
local_version = local_version,
|
||||
remote_version = remote_version,
|
||||
html_url = json.html_url,
|
||||
data = asset
|
||||
}
|
||||
end
|
||||
|
||||
function to_download(app_name, url, size)
|
||||
local result = check_path(app_name)
|
||||
if result.code ~= 0 then
|
||||
return result
|
||||
end
|
||||
|
||||
if not url or url == "" then
|
||||
return {code = 1, error = i18n.translate("Download url is required.")}
|
||||
end
|
||||
|
||||
sys.call("/bin/rm -f /tmp/".. app_name .."_download.*")
|
||||
|
||||
local tmp_file = util.trim(util.exec("mktemp -u -t ".. app_name .."_download.XXXXXX"))
|
||||
|
||||
if size then
|
||||
local kb1 = get_free_space("/tmp")
|
||||
if tonumber(size) > tonumber(kb1) then
|
||||
return {code = 1, error = i18n.translatef("%s not enough space.", "/tmp")}
|
||||
end
|
||||
end
|
||||
|
||||
local return_code, result = curl_logic(url, tmp_file, curl_args)
|
||||
result = return_code == 0
|
||||
|
||||
if not result then
|
||||
exec("/bin/rm", {"-f", tmp_file})
|
||||
return {
|
||||
code = 1,
|
||||
error = i18n.translatef("File download failed or timed out: %s", url)
|
||||
}
|
||||
end
|
||||
|
||||
return {code = 0, file = tmp_file, zip = com[app_name].zipped }
|
||||
end
|
||||
|
||||
function to_extract(app_name, file, subfix)
|
||||
local result = check_path(app_name)
|
||||
if result.code ~= 0 then
|
||||
return result
|
||||
end
|
||||
|
||||
if not file or file == "" or not fs.access(file) then
|
||||
return {code = 1, error = i18n.translate("File path required.")}
|
||||
end
|
||||
|
||||
if sys.exec("echo -n $(opkg list-installed | grep -c unzip)") ~= "1" then
|
||||
exec("/bin/rm", {"-f", file})
|
||||
return {
|
||||
code = 1,
|
||||
error = i18n.translate("Not installed unzip, Can't unzip!")
|
||||
}
|
||||
end
|
||||
|
||||
sys.call("/bin/rm -rf /tmp/".. app_name .."_extract.*")
|
||||
|
||||
local new_file_size = get_file_space(file)
|
||||
local tmp_free_size = get_free_space("/tmp")
|
||||
if tmp_free_size <= 0 or tmp_free_size <= new_file_size then
|
||||
return {code = 1, error = i18n.translatef("%s not enough space.", "/tmp")}
|
||||
end
|
||||
|
||||
local tmp_dir = util.trim(util.exec("mktemp -d -t ".. app_name .."_extract.XXXXXX"))
|
||||
|
||||
local output = {}
|
||||
exec("/usr/bin/unzip", {"-o", file, app_name, "-d", tmp_dir},
|
||||
function(chunk) output[#output + 1] = chunk end)
|
||||
|
||||
local files = util.split(table.concat(output))
|
||||
|
||||
exec("/bin/rm", {"-f", file})
|
||||
|
||||
return {code = 0, file = tmp_dir}
|
||||
end
|
||||
|
||||
function to_move(app_name,file)
|
||||
local result = check_path(app_name)
|
||||
if result.code ~= 0 then
|
||||
return result
|
||||
end
|
||||
|
||||
local app_path = result.app_path
|
||||
local bin_path = file
|
||||
local cmd_rm_tmp = "/bin/rm -rf /tmp/" .. app_name .. "_download.*"
|
||||
if fs.stat(file, "type") == "dir" then
|
||||
bin_path = file .. "/" .. app_name
|
||||
cmd_rm_tmp = "/bin/rm -rf /tmp/" .. app_name .. "_extract.*"
|
||||
end
|
||||
|
||||
if not file or file == "" then
|
||||
sys.call(cmd_rm_tmp)
|
||||
return {code = 1, error = i18n.translate("Client file is required.")}
|
||||
end
|
||||
|
||||
local new_version = get_app_version(app_name, bin_path)
|
||||
if new_version == "" then
|
||||
sys.call(cmd_rm_tmp)
|
||||
return {
|
||||
code = 1,
|
||||
error = i18n.translate("The client file is not suitable for current device.")..app_name.."__"..bin_path
|
||||
}
|
||||
end
|
||||
|
||||
local flag = sys.call('pgrep -af "passwall2/.*'.. app_name ..'" >/dev/null')
|
||||
if flag == 0 then
|
||||
sys.call("/etc/init.d/passwall2 stop")
|
||||
end
|
||||
|
||||
local old_app_size = 0
|
||||
if fs.access(app_path) then
|
||||
old_app_size = get_file_space(app_path)
|
||||
end
|
||||
local new_app_size = get_file_space(bin_path)
|
||||
local final_dir = get_final_dir(app_path)
|
||||
local final_dir_free_size = get_free_space(final_dir)
|
||||
if final_dir_free_size > 0 then
|
||||
final_dir_free_size = final_dir_free_size + old_app_size
|
||||
if new_app_size > final_dir_free_size then
|
||||
sys.call(cmd_rm_tmp)
|
||||
return {code = 1, error = i18n.translatef("%s not enough space.", final_dir)}
|
||||
end
|
||||
end
|
||||
|
||||
result = exec("/bin/mv", { "-f", bin_path, app_path }, nil, command_timeout) == 0
|
||||
|
||||
sys.call(cmd_rm_tmp)
|
||||
if flag == 0 then
|
||||
sys.call("/etc/init.d/passwall2 restart >/dev/null 2>&1 &")
|
||||
end
|
||||
|
||||
if not result or not fs.access(app_path) then
|
||||
return {
|
||||
code = 1,
|
||||
error = i18n.translatef("Can't move new file to path: %s", app_path)
|
||||
}
|
||||
end
|
||||
|
||||
return {code = 0}
|
||||
end
|
63
luci-app-passwall2/luasrc/passwall2/com.lua
Normal file
63
luci-app-passwall2/luasrc/passwall2/com.lua
Normal file
@ -0,0 +1,63 @@
|
||||
local _M = {}
|
||||
|
||||
local function gh_release_url(self)
|
||||
return "https://api.github.com/repos/" .. self.repo .. "/releases/latest"
|
||||
end
|
||||
|
||||
local function gh_pre_release_url(self)
|
||||
return "https://api.github.com/repos/" .. self.repo .. "/releases?per_page=1"
|
||||
end
|
||||
|
||||
_M.brook = {
|
||||
name = "Brook",
|
||||
repo = "txthinking/brook",
|
||||
get_url = gh_release_url,
|
||||
cmd_version = "-v | awk '{print $3}'",
|
||||
zipped = false,
|
||||
default_path = "/usr/bin/brook",
|
||||
match_fmt_str = "linux_%s$",
|
||||
file_tree = {}
|
||||
}
|
||||
|
||||
_M.hysteria = {
|
||||
name = "Hysteria",
|
||||
repo = "HyNetwork/hysteria",
|
||||
get_url = gh_release_url,
|
||||
cmd_version = "-v | awk '{print $3}'",
|
||||
zipped = false,
|
||||
default_path = "/usr/bin/hysteria",
|
||||
match_fmt_str = "linux%%-%s$",
|
||||
file_tree = {
|
||||
armv6 = "arm",
|
||||
armv7 = "arm"
|
||||
}
|
||||
}
|
||||
|
||||
_M.v2ray = {
|
||||
name = "V2ray",
|
||||
repo = "v2fly/v2ray-core",
|
||||
get_url = gh_pre_release_url,
|
||||
cmd_version = "version | awk '{print $2}' | sed -n 1P",
|
||||
zipped = true,
|
||||
default_path = "/usr/bin/v2ray",
|
||||
match_fmt_str = "linux%%-%s",
|
||||
file_tree = {
|
||||
x86_64 = "64",
|
||||
x86 = "32",
|
||||
mips = "mips32",
|
||||
mipsel = "mips32le"
|
||||
}
|
||||
}
|
||||
|
||||
_M.xray = {
|
||||
name = "Xray",
|
||||
repo = "XTLS/Xray-core",
|
||||
get_url = gh_pre_release_url,
|
||||
cmd_version = _M.v2ray.cmd_version,
|
||||
zipped = true,
|
||||
default_path = "/usr/bin/xray",
|
||||
match_fmt_str = _M.v2ray.match_fmt_str,
|
||||
file_tree = _M.v2ray.file_tree
|
||||
}
|
||||
|
||||
return _M
|
230
luci-app-passwall2/luasrc/passwall2/server_app.lua
Normal file
230
luci-app-passwall2/luasrc/passwall2/server_app.lua
Normal file
@ -0,0 +1,230 @@
|
||||
#!/usr/bin/lua
|
||||
|
||||
local action = arg[1]
|
||||
local api = require "luci.passwall2.api"
|
||||
local sys = api.sys
|
||||
local uci = api.uci
|
||||
local jsonc = api.jsonc
|
||||
|
||||
local CONFIG = "passwall2_server"
|
||||
local CONFIG_PATH = "/tmp/etc/" .. CONFIG
|
||||
local NFT_INCLUDE_FILE = CONFIG_PATH .. "/" .. CONFIG .. ".nft"
|
||||
local LOG_APP_FILE = "/tmp/log/" .. CONFIG .. ".log"
|
||||
local TMP_BIN_PATH = CONFIG_PATH .. "/bin"
|
||||
local require_dir = "luci.passwall2."
|
||||
|
||||
local ipt_bin = sys.exec("echo -n $(/usr/share/passwall2/iptables.sh get_ipt_bin)")
|
||||
local ip6t_bin = sys.exec("echo -n $(/usr/share/passwall2/iptables.sh get_ip6t_bin)")
|
||||
|
||||
local nft_flag = api.is_finded("fw4") and "1" or "0"
|
||||
|
||||
local function log(...)
|
||||
local f, err = io.open(LOG_APP_FILE, "a")
|
||||
if f and err == nil then
|
||||
local str = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ")
|
||||
f:write(str .. "\n")
|
||||
f:close()
|
||||
end
|
||||
end
|
||||
|
||||
local function cmd(cmd)
|
||||
sys.call(cmd)
|
||||
end
|
||||
|
||||
local function ipt(arg)
|
||||
if ipt_bin and #ipt_bin > 0 then
|
||||
cmd(ipt_bin .. " -w " .. arg)
|
||||
end
|
||||
end
|
||||
|
||||
local function ip6t(arg)
|
||||
if ip6t_bin and #ip6t_bin > 0 then
|
||||
cmd(ip6t_bin .. " -w " .. arg)
|
||||
end
|
||||
end
|
||||
|
||||
local function ln_run(s, d, command, output)
|
||||
if not output then
|
||||
output = "/dev/null"
|
||||
end
|
||||
d = TMP_BIN_PATH .. "/" .. d
|
||||
cmd(string.format('[ ! -f "%s" ] && ln -s %s %s 2>/dev/null', d, s, d))
|
||||
return string.format("%s >%s 2>&1 &", d .. " " ..command, output)
|
||||
end
|
||||
|
||||
local function gen_include()
|
||||
cmd(string.format("echo '#!/bin/sh' > /tmp/etc/%s.include", CONFIG))
|
||||
local function extract_rules(n, a)
|
||||
local _ipt = ipt_bin
|
||||
if n == "6" then
|
||||
_ipt = ip6t_bin
|
||||
end
|
||||
local result = "*" .. a
|
||||
result = result .. "\n" .. sys.exec(_ipt .. '-save -t ' .. a .. ' | grep "PSW2-SERVER" | sed -e "s/^-A \\(INPUT\\)/-I \\1 1/"')
|
||||
result = result .. "COMMIT"
|
||||
return result
|
||||
end
|
||||
local f, err = io.open("/tmp/etc/" .. CONFIG .. ".include", "a")
|
||||
if f and err == nil then
|
||||
if nft_flag == "0" then
|
||||
f:write(ipt_bin .. '-save -c | grep -v "PSW2-SERVER" | ' .. ipt_bin .. '-restore -c' .. "\n")
|
||||
f:write(ipt_bin .. '-restore -n <<-EOT' .. "\n")
|
||||
f:write(extract_rules("4", "filter") .. "\n")
|
||||
f:write("EOT" .. "\n")
|
||||
f:write(ip6t_bin .. '-save -c | grep -v "PSW2-SERVER" | ' .. ip6t_bin .. '-restore -c' .. "\n")
|
||||
f:write(ip6t_bin .. '-restore -n <<-EOT' .. "\n")
|
||||
f:write(extract_rules("6", "filter") .. "\n")
|
||||
f:write("EOT" .. "\n")
|
||||
f:close()
|
||||
else
|
||||
f:write("nft -f " .. NFT_INCLUDE_FILE .. "\n")
|
||||
f:close()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function start()
|
||||
local enabled = tonumber(uci:get(CONFIG, "@global[0]", "enable") or 0)
|
||||
if enabled == nil or enabled == 0 then
|
||||
return
|
||||
end
|
||||
cmd(string.format("mkdir -p %s %s", CONFIG_PATH, TMP_BIN_PATH))
|
||||
cmd(string.format("touch %s", LOG_APP_FILE))
|
||||
if nft_flag == "0" then
|
||||
ipt("-N PSW2-SERVER")
|
||||
ipt("-I INPUT -j PSW2-SERVER")
|
||||
ip6t("-N PSW2-SERVER")
|
||||
ip6t("-I INPUT -j PSW2-SERVER")
|
||||
else
|
||||
nft_file, err = io.open(NFT_INCLUDE_FILE, "w")
|
||||
nft_file:write('#!/usr/sbin/nft -f\n')
|
||||
nft_file:write('add chain inet fw4 PSW2-SERVER\n')
|
||||
nft_file:write('flush chain inet fw4 PSW2-SERVER\n')
|
||||
nft_file:write('insert rule inet fw4 input position 0 jump PSW2-SERVER comment "PSW2-SERVER"\n')
|
||||
end
|
||||
uci:foreach(CONFIG, "user", function(user)
|
||||
local id = user[".name"]
|
||||
local enable = user.enable
|
||||
if enable and tonumber(enable) == 1 then
|
||||
local enable_log = user.log
|
||||
local log_path = nil
|
||||
if enable_log and enable_log == "1" then
|
||||
log_path = CONFIG_PATH .. "/" .. id .. ".log"
|
||||
else
|
||||
log_path = nil
|
||||
end
|
||||
local remarks = user.remarks
|
||||
local port = tonumber(user.port)
|
||||
local bin
|
||||
local config = {}
|
||||
local config_file = CONFIG_PATH .. "/" .. id .. ".json"
|
||||
local udp_forward = 1
|
||||
local type = user.type or ""
|
||||
if type == "Socks" then
|
||||
local auth = ""
|
||||
if user.auth and user.auth == "1" then
|
||||
local username = user.username or ""
|
||||
local password = user.password or ""
|
||||
if username ~= "" and password ~= "" then
|
||||
username = "-u " .. username
|
||||
password = "-P " .. password
|
||||
auth = username .. " " .. password
|
||||
end
|
||||
end
|
||||
bin = ln_run("/usr/bin/microsocks", "microsocks_" .. id, string.format("-i :: -p %s %s", port, auth), log_path)
|
||||
elseif type == "SS" or type == "SSR" then
|
||||
config = require(require_dir .. "util_shadowsocks").gen_config_server(user)
|
||||
local udp_param = ""
|
||||
udp_forward = tonumber(user.udp_forward) or 1
|
||||
if udp_forward == 1 then
|
||||
udp_param = "-u"
|
||||
end
|
||||
type = type:lower()
|
||||
bin = ln_run("/usr/bin/" .. type .. "-server", type .. "-server", "-c " .. config_file .. " " .. udp_param, log_path)
|
||||
elseif type == "SS-Rust" then
|
||||
config = require(require_dir .. "util_shadowsocks").gen_config_server(user)
|
||||
bin = ln_run("/usr/bin/ssserver", "ssserver", "-c " .. config_file, log_path)
|
||||
elseif type == "V2ray" then
|
||||
config = require(require_dir .. "util_xray").gen_config_server(user)
|
||||
bin = ln_run(api.get_app_path("v2ray"), "v2ray", "run -c " .. config_file, log_path)
|
||||
elseif type == "Xray" then
|
||||
config = require(require_dir .. "util_xray").gen_config_server(user)
|
||||
bin = ln_run(api.get_app_path("xray"), "xray", "run -c " .. config_file, log_path)
|
||||
elseif type == "Brook" then
|
||||
local brook_protocol = user.protocol
|
||||
local brook_password = user.password
|
||||
local brook_path = user.ws_path or "/ws"
|
||||
local brook_path_arg = ""
|
||||
if brook_protocol == "wsserver" and brook_path then
|
||||
brook_path_arg = " --path " .. brook_path
|
||||
end
|
||||
bin = ln_run(api.get_app_path("brook"), "brook_" .. id, string.format("--debug %s -l :%s -p %s%s", brook_protocol, port, brook_password, brook_path_arg), log_path)
|
||||
elseif type == "Hysteria" then
|
||||
config = require(require_dir .. "util_hysteria").gen_config_server(user)
|
||||
bin = ln_run(api.get_app_path("hysteria"), "hysteria", "-c " .. config_file .. " server", log_path)
|
||||
end
|
||||
|
||||
if next(config) then
|
||||
local f, err = io.open(config_file, "w")
|
||||
if f and err == nil then
|
||||
f:write(jsonc.stringify(config, 1))
|
||||
f:close()
|
||||
end
|
||||
log(string.format("%s %s 生成配置文件并运行 - %s", remarks, port, config_file))
|
||||
end
|
||||
|
||||
if bin then
|
||||
cmd(bin)
|
||||
end
|
||||
|
||||
local bind_local = user.bind_local or 0
|
||||
if bind_local and tonumber(bind_local) ~= 1 then
|
||||
if nft_flag == "0" then
|
||||
ipt(string.format('-A PSW2-SERVER -p tcp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks))
|
||||
ip6t(string.format('-A PSW2-SERVER -p tcp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks))
|
||||
if udp_forward == 1 then
|
||||
ipt(string.format('-A PSW2-SERVER -p udp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks))
|
||||
ip6t(string.format('-A PSW2-SERVER -p udp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks))
|
||||
end
|
||||
else
|
||||
nft_file:write(string.format('add rule inet fw4 PSW2-SERVER meta l4proto tcp tcp dport {%s} counter accept comment "%s"\n', port, remarks))
|
||||
if udp_forward == 1 then
|
||||
nft_file:write(string.format('add rule inet fw4 PSW2-SERVER meta l4proto udp udp dport {%s} counter accept comment "%s"\n', port, remarks))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
if nft_flag == "1" then
|
||||
nft_file:write("add rule inet fw4 PSW2-SERVER return\n")
|
||||
nft_file:close()
|
||||
cmd("nft -f " .. NFT_INCLUDE_FILE)
|
||||
end
|
||||
gen_include()
|
||||
end
|
||||
|
||||
local function stop()
|
||||
cmd(string.format("top -bn1 | grep -v 'grep' | grep '%s/' | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1", CONFIG_PATH))
|
||||
if nft_flag == "0" then
|
||||
ipt("-D INPUT -j PSW2-SERVER 2>/dev/null")
|
||||
ipt("-F PSW2-SERVER 2>/dev/null")
|
||||
ipt("-X PSW2-SERVER 2>/dev/null")
|
||||
ip6t("-D INPUT -j PSW2-SERVER 2>/dev/null")
|
||||
ip6t("-F PSW2-SERVER 2>/dev/null")
|
||||
ip6t("-X PSW2-SERVER 2>/dev/null")
|
||||
else
|
||||
local nft_cmd = "handles=$(nft -a list chain inet fw4 input | grep -E \"PSW2-SERVER\" | awk -F '# handle ' '{print$2}')\n for handle in $handles; do\n nft delete rule inet fw4 input handle ${handle} 2>/dev/null\n done"
|
||||
cmd(nft_cmd)
|
||||
cmd("nft flush chain inet fw4 PSW2-SERVER 2>/dev/null")
|
||||
cmd("nft delete chain inet fw4 PSW2-SERVER 2>/dev/null")
|
||||
end
|
||||
cmd(string.format("rm -rf %s %s /tmp/etc/%s.include", CONFIG_PATH, LOG_APP_FILE, CONFIG))
|
||||
end
|
||||
|
||||
if action then
|
||||
if action == "start" then
|
||||
start()
|
||||
elseif action == "stop" then
|
||||
stop()
|
||||
end
|
||||
end
|
104
luci-app-passwall2/luasrc/passwall2/util_hysteria.lua
Normal file
104
luci-app-passwall2/luasrc/passwall2/util_hysteria.lua
Normal file
@ -0,0 +1,104 @@
|
||||
module("luci.passwall2.util_hysteria", package.seeall)
|
||||
local api = require "luci.passwall2.api"
|
||||
local uci = api.uci
|
||||
local jsonc = api.jsonc
|
||||
|
||||
function gen_config_server(node)
|
||||
local config = {
|
||||
listen = ":" .. node.port,
|
||||
protocol = node.protocol or "udp",
|
||||
obfs = node.hysteria_obfs,
|
||||
cert = node.tls_certificateFile,
|
||||
key = node.tls_keyFile,
|
||||
auth = (node.hysteria_auth_type == "string") and {
|
||||
mode = "password",
|
||||
config = {
|
||||
password = node.hysteria_auth_password
|
||||
}
|
||||
} or nil,
|
||||
disable_udp = (node.hysteria_udp == "0") and true or false,
|
||||
alpn = node.hysteria_alpn or nil,
|
||||
up_mbps = tonumber(node.hysteria_up_mbps) or 10,
|
||||
down_mbps = tonumber(node.hysteria_down_mbps) or 50,
|
||||
recv_window_conn = (node.hysteria_recv_window_conn) and tonumber(node.hysteria_recv_window_conn) or nil,
|
||||
recv_window = (node.hysteria_recv_window) and tonumber(node.hysteria_recv_window) or nil,
|
||||
disable_mtu_discovery = (node.hysteria_disable_mtu_discovery) and true or false
|
||||
}
|
||||
return config
|
||||
end
|
||||
|
||||
function gen_config(var)
|
||||
local node_id = var["-node"]
|
||||
if not node_id then
|
||||
print("-node 不能为空")
|
||||
return
|
||||
end
|
||||
local node = uci:get_all("passwall2", node_id)
|
||||
local local_socks_address = var["-local_socks_address"] or "0.0.0.0"
|
||||
local local_socks_port = var["-local_socks_port"]
|
||||
local local_socks_username = var["-local_socks_username"]
|
||||
local local_socks_password = var["-local_socks_password"]
|
||||
local local_http_address = var["-local_http_address"] or "0.0.0.0"
|
||||
local local_http_port = var["-local_http_port"]
|
||||
local local_http_username = var["-local_http_username"]
|
||||
local local_http_password = var["-local_http_password"]
|
||||
local server_host = var["-server_host"] or node.address
|
||||
local server_port = var["-server_port"] or node.port
|
||||
|
||||
if api.is_ipv6(server_host) then
|
||||
server_host = api.get_ipv6_full(server_host)
|
||||
end
|
||||
local server = server_host .. ":" .. server_port
|
||||
|
||||
if (node.hysteria_hop) then
|
||||
server = server .. "," .. node.hysteria_hop
|
||||
end
|
||||
|
||||
local config = {
|
||||
server = server,
|
||||
protocol = node.protocol or "udp",
|
||||
obfs = node.hysteria_obfs,
|
||||
auth = (node.hysteria_auth_type == "base64") and node.hysteria_auth_password or nil,
|
||||
auth_str = (node.hysteria_auth_type == "string") and node.hysteria_auth_password or nil,
|
||||
alpn = node.hysteria_alpn or nil,
|
||||
server_name = node.tls_serverName,
|
||||
insecure = (node.tls_allowInsecure == "1") and true or false,
|
||||
up_mbps = tonumber(node.hysteria_up_mbps) or 10,
|
||||
down_mbps = tonumber(node.hysteria_down_mbps) or 50,
|
||||
retry = -1,
|
||||
retry_interval = 5,
|
||||
recv_window_conn = (node.hysteria_recv_window_conn) and tonumber(node.hysteria_recv_window_conn) or nil,
|
||||
recv_window = (node.hysteria_recv_window) and tonumber(node.hysteria_recv_window) or nil,
|
||||
handshake_timeout = (node.hysteria_handshake_timeout) and tonumber(node.hysteria_handshake_timeout) or nil,
|
||||
idle_timeout = (node.hysteria_idle_timeout) and tonumber(node.hysteria_idle_timeout) or nil,
|
||||
hop_interval = (node.hysteria_hop_interval) and tonumber(node.hysteria_hop_interval) or nil,
|
||||
disable_mtu_discovery = (node.hysteria_disable_mtu_discovery) and true or false,
|
||||
fast_open = (node.fast_open == "1") and true or false,
|
||||
lazy_start = (node.hysteria_lazy_start) and true or false,
|
||||
socks5 = (local_socks_address and local_socks_port) and {
|
||||
listen = local_socks_address .. ":" .. local_socks_port,
|
||||
timeout = 300,
|
||||
disable_udp = false,
|
||||
user = (local_socks_username and local_socks_password) and local_socks_username,
|
||||
password = (local_socks_username and local_socks_password) and local_socks_password,
|
||||
} or nil,
|
||||
http = (local_http_address and local_http_port) and {
|
||||
listen = local_http_address .. ":" .. local_http_port,
|
||||
timeout = 300,
|
||||
disable_udp = false,
|
||||
user = (local_http_username and local_http_password) and local_http_username,
|
||||
password = (local_http_username and local_http_password) and local_http_password,
|
||||
} or nil
|
||||
}
|
||||
|
||||
return jsonc.stringify(config, 1)
|
||||
end
|
||||
|
||||
_G.gen_config = gen_config
|
||||
|
||||
if arg[1] then
|
||||
local func =_G[arg[1]]
|
||||
if func then
|
||||
print(func(api.get_function_args(arg)))
|
||||
end
|
||||
end
|
39
luci-app-passwall2/luasrc/passwall2/util_naiveproxy.lua
Normal file
39
luci-app-passwall2/luasrc/passwall2/util_naiveproxy.lua
Normal file
@ -0,0 +1,39 @@
|
||||
module("luci.passwall2.util_navieproxy", package.seeall)
|
||||
local api = require "luci.passwall2.api"
|
||||
local uci = api.uci
|
||||
local jsonc = api.jsonc
|
||||
|
||||
function gen_config(var)
|
||||
local node_id = var["-node"]
|
||||
if not node_id then
|
||||
print("-node 不能为空")
|
||||
return
|
||||
end
|
||||
local node = uci:get_all("passwall2", node_id)
|
||||
local run_type = var["-run_type"]
|
||||
local local_addr = var["-local_addr"]
|
||||
local local_port = var["-local_port"]
|
||||
local server_host = var["-server_host"] or node.address
|
||||
local server_port = var["-server_port"] or node.port
|
||||
|
||||
if api.is_ipv6(server_host) then
|
||||
server_host = api.get_ipv6_full(server_host)
|
||||
end
|
||||
local server = server_host .. ":" .. server_port
|
||||
|
||||
local config = {
|
||||
listen = run_type .. "://" .. local_addr .. ":" .. local_port,
|
||||
proxy = node.protocol .. "://" .. node.username .. ":" .. node.password .. "@" .. server
|
||||
}
|
||||
|
||||
return jsonc.stringify(config, 1)
|
||||
end
|
||||
|
||||
_G.gen_config = gen_config
|
||||
|
||||
if arg[1] then
|
||||
local func =_G[arg[1]]
|
||||
if func then
|
||||
print(func(api.get_function_args(arg)))
|
||||
end
|
||||
end
|
123
luci-app-passwall2/luasrc/passwall2/util_shadowsocks.lua
Normal file
123
luci-app-passwall2/luasrc/passwall2/util_shadowsocks.lua
Normal file
@ -0,0 +1,123 @@
|
||||
module("luci.passwall2.util_shadowsocks", package.seeall)
|
||||
local api = require "luci.passwall2.api"
|
||||
local uci = api.uci
|
||||
local jsonc = api.jsonc
|
||||
|
||||
function gen_config_server(node)
|
||||
local config = {}
|
||||
config.server_port = tonumber(node.port)
|
||||
config.password = node.password
|
||||
config.timeout = tonumber(node.timeout)
|
||||
config.fast_open = (node.tcp_fast_open and node.tcp_fast_open == "1") and true or false
|
||||
config.method = node.method
|
||||
|
||||
if node.type == "SS-Rust" then
|
||||
config.server = "::"
|
||||
config.mode = "tcp_and_udp"
|
||||
else
|
||||
config.server = {"[::0]", "0.0.0.0"}
|
||||
end
|
||||
|
||||
if node.type == "SSR" then
|
||||
config.protocol = node.protocol
|
||||
config.protocol_param = node.protocol_param
|
||||
config.obfs = node.obfs
|
||||
config.obfs_param = node.obfs_param
|
||||
end
|
||||
|
||||
return config
|
||||
end
|
||||
|
||||
|
||||
function gen_config(var)
|
||||
local node_id = var["-node"]
|
||||
if not node_id then
|
||||
print("-node 不能为空")
|
||||
return
|
||||
end
|
||||
local node = uci:get_all("passwall2", node_id)
|
||||
local server_host = var["-server_host"] or node.address
|
||||
local server_port = var["-server_port"] or node.port
|
||||
local local_addr = var["-local_addr"]
|
||||
local local_port = var["-local_port"]
|
||||
local mode = var["-mode"]
|
||||
local local_socks_address = var["-local_socks_address"] or "0.0.0.0"
|
||||
local local_socks_port = var["-local_socks_port"]
|
||||
local local_socks_username = var["-local_socks_username"]
|
||||
local local_socks_password = var["-local_socks_password"]
|
||||
local local_http_address = var["-local_http_address"] or "0.0.0.0"
|
||||
local local_http_port = var["-local_http_port"]
|
||||
local local_http_username = var["-local_http_username"]
|
||||
local local_http_password = var["-local_http_password"]
|
||||
|
||||
if api.is_ipv6(server_host) then
|
||||
server_host = api.get_ipv6_only(server_host)
|
||||
end
|
||||
local server = server_host
|
||||
|
||||
local config = {
|
||||
server = server,
|
||||
server_port = tonumber(server_port),
|
||||
local_address = local_addr,
|
||||
local_port = tonumber(local_port),
|
||||
password = node.password,
|
||||
method = node.method,
|
||||
timeout = tonumber(node.timeout),
|
||||
fast_open = (node.tcp_fast_open and node.tcp_fast_open == "true") and true or false,
|
||||
reuse_port = true
|
||||
}
|
||||
|
||||
if node.type == "SS" then
|
||||
if node.plugin and node.plugin ~= "none" then
|
||||
config.plugin = node.plugin
|
||||
config.plugin_opts = node.plugin_opts or nil
|
||||
end
|
||||
config.mode = mode
|
||||
elseif node.type == "SSR" then
|
||||
config.protocol = node.protocol
|
||||
config.protocol_param = node.protocol_param
|
||||
config.obfs = node.obfs
|
||||
config.obfs_param = node.obfs_param
|
||||
elseif node.type == "SS-Rust" then
|
||||
config = {
|
||||
servers = {
|
||||
{
|
||||
address = server,
|
||||
port = tonumber(server_port),
|
||||
method = node.method,
|
||||
password = node.password,
|
||||
timeout = tonumber(node.timeout),
|
||||
plugin = (node.plugin and node.plugin ~= "none") and node.plugin or nil,
|
||||
plugin_opts = (node.plugin and node.plugin ~= "none") and node.plugin_opts or nil
|
||||
}
|
||||
},
|
||||
locals = {},
|
||||
fast_open = (node.tcp_fast_open and node.tcp_fast_open == "true") and true or false
|
||||
}
|
||||
if local_socks_address and local_socks_port then
|
||||
table.insert(config.locals, {
|
||||
local_address = local_socks_address,
|
||||
local_port = tonumber(local_socks_port),
|
||||
mode = "tcp_and_udp"
|
||||
})
|
||||
end
|
||||
if local_http_address and local_http_port then
|
||||
table.insert(config.locals, {
|
||||
protocol = "http",
|
||||
local_address = local_http_address,
|
||||
local_port = tonumber(local_http_port)
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return jsonc.stringify(config, 1)
|
||||
end
|
||||
|
||||
_G.gen_config = gen_config
|
||||
|
||||
if arg[1] then
|
||||
local func =_G[arg[1]]
|
||||
if func then
|
||||
print(func(api.get_function_args(arg)))
|
||||
end
|
||||
end
|
1623
luci-app-passwall2/luasrc/passwall2/util_xray.lua
Normal file
1623
luci-app-passwall2/luasrc/passwall2/util_xray.lua
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,192 @@
|
||||
<%
|
||||
local api = require "luci.passwall2.api"
|
||||
local com = require "luci.passwall2.com"
|
||||
local version = {}
|
||||
-%>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
var appInfoList = new Array();
|
||||
var inProgressCount = 0;
|
||||
var tokenStr = '<%=token%>';
|
||||
var checkUpdateText = '<%:Check update%>';
|
||||
var noUpdateText = '<%:It is the latest version%>';
|
||||
var updateSuccessText = '<%:Update successful%>';
|
||||
var clickToUpdateText = '<%:Click to update%>';
|
||||
var inProgressText = '<%:Updating...%>';
|
||||
var unexpectedErrorText = '<%:Unexpected error%>';
|
||||
var updateInProgressNotice = '<%:Updating, are you sure to close?%>';
|
||||
var downloadingText = '<%:Downloading...%>';
|
||||
var decompressioningText = '<%:Unpacking...%>';
|
||||
var movingText = '<%:Moving...%>';
|
||||
|
||||
//window.onload = function () {};
|
||||
|
||||
function addPageNotice() {
|
||||
if (inProgressCount === 0) {
|
||||
window.onbeforeunload = function (e) {
|
||||
e.returnValue = updateInProgressNotice;
|
||||
return updateInProgressNotice;
|
||||
};
|
||||
}
|
||||
inProgressCount++;
|
||||
}
|
||||
|
||||
function removePageNotice() {
|
||||
inProgressCount--;
|
||||
if (inProgressCount === 0) {
|
||||
window.onbeforeunload = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function onUpdateSuccess(btn) {
|
||||
if (btn) {
|
||||
btn.value = updateSuccessText;
|
||||
btn.placeholder = updateSuccessText;
|
||||
btn.disabled = true;
|
||||
}
|
||||
|
||||
if (inProgressCount === 0) {
|
||||
window.setTimeout(function () {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function onRequestError(btn, errorMessage) {
|
||||
btn.disabled = false;
|
||||
btn.value = checkUpdateText;
|
||||
|
||||
var ckeckDetailElm = document.getElementById(btn.id + '-detail');
|
||||
if (errorMessage && ckeckDetailElm) {
|
||||
ckeckDetailElm.textContent = errorMessage
|
||||
}
|
||||
}
|
||||
|
||||
function onBtnClick(btn, app) {
|
||||
if (appInfoList[app] === undefined) {
|
||||
checkUpdate(btn, app);
|
||||
} else {
|
||||
doUpdate(btn, app);
|
||||
}
|
||||
}
|
||||
|
||||
function checkUpdate(btn, app) {
|
||||
btn.disabled = true;
|
||||
btn.value = inProgressText;
|
||||
|
||||
addPageNotice();
|
||||
|
||||
var ckeckDetailElm = document.getElementById(btn.id + '-detail');
|
||||
if (ckeckDetailElm) {
|
||||
ckeckDetailElm.textContent = "";
|
||||
}
|
||||
XHR.get('<%=api.url("check_")%>' + app, {
|
||||
token: tokenStr,
|
||||
arch: ''
|
||||
}, function (x, json) {
|
||||
removePageNotice();
|
||||
if (json.code) {
|
||||
appInfoList[app] = undefined;
|
||||
onRequestError(btn, json.error);
|
||||
} else {
|
||||
if (json.has_update) {
|
||||
appInfoList[app] = json;
|
||||
btn.disabled = false;
|
||||
btn.value = clickToUpdateText;
|
||||
btn.placeholder = clickToUpdateText;
|
||||
|
||||
if (ckeckDetailElm) {
|
||||
var urlNode = '';
|
||||
if (json.remote_version) {
|
||||
urlNode = '<em style="color:red;">' + json.remote_version + '</em>';
|
||||
if (json.html_url) {
|
||||
urlNode = '<a href="' + json.html_url + '" target="_blank">' + urlNode + '</a>';
|
||||
}
|
||||
}
|
||||
ckeckDetailElm.innerHTML = urlNode;
|
||||
}
|
||||
} else {
|
||||
btn.disabled = true;
|
||||
btn.value = noUpdateText;
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function doUpdate(btn, app) {
|
||||
btn.disabled = true;
|
||||
btn.value = downloadingText;
|
||||
|
||||
addPageNotice();
|
||||
|
||||
var appUpdateUrl = '<%=api.url("update_")%>' + app;
|
||||
var appInfo = appInfoList[app];
|
||||
// Download file
|
||||
XHR.get(appUpdateUrl, {
|
||||
token: tokenStr,
|
||||
url: appInfo ? appInfo.data.browser_download_url : '',
|
||||
size: appInfo ? appInfo.data.size / 1024 : null
|
||||
}, function (x, json) {
|
||||
if (json.code) {
|
||||
removePageNotice();
|
||||
onRequestError(btn, json.error);
|
||||
} else if (json.zip) {
|
||||
btn.value = decompressioningText;
|
||||
|
||||
// Extract file
|
||||
XHR.get(appUpdateUrl, {
|
||||
token: tokenStr,
|
||||
task: 'extract',
|
||||
file: json.file,
|
||||
subfix: appInfo ? appInfo.type : ''
|
||||
}, function (x, json) {
|
||||
if (json.code) {
|
||||
removePageNotice();
|
||||
onRequestError(btn, json.error);
|
||||
} else {
|
||||
move(btn, appUpdateUrl, json.file);
|
||||
}
|
||||
}, 300)
|
||||
} else {
|
||||
move(btn, appUpdateUrl, json.file);
|
||||
}
|
||||
}, 300)
|
||||
}
|
||||
|
||||
function move(btn, url, file) {
|
||||
btn.value = movingText;
|
||||
|
||||
// Move file to target dir
|
||||
XHR.get(url, {
|
||||
token: tokenStr,
|
||||
task: 'move',
|
||||
file: file
|
||||
}, function (x, json) {
|
||||
removePageNotice();
|
||||
if (json.code) {
|
||||
onRequestError(btn, json.error);
|
||||
} else {
|
||||
onUpdateSuccess(btn);
|
||||
}
|
||||
}, 300)
|
||||
}
|
||||
//]]>
|
||||
</script>
|
||||
|
||||
<%for k, v in pairs(com) do
|
||||
version[k] = api.get_app_version(k)%>
|
||||
<div class="cbi-value">
|
||||
<label class="cbi-value-title"><%=v.name%>
|
||||
<%:Version%>
|
||||
</label>
|
||||
<div class="cbi-value-field">
|
||||
<div class="cbi-value-description">
|
||||
<span>【 <%=version[k] ~="" and version[k] or translate("Null") %> 】</span>
|
||||
<input class="btn cbi-button cbi-button-apply" type="button" id="_<%=k%>-check_btn"
|
||||
onclick="onBtnClick(this,'<%=k%>');" value="<%:Check update%>" />
|
||||
<span id="_<%=k%>-check_btn-detail"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%end%>
|
@ -0,0 +1,22 @@
|
||||
<%
|
||||
local api = require "luci.passwall2.api"
|
||||
-%>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
function add_node_by_key() {
|
||||
var key = prompt("<%:Please enter the node keyword, pay attention to distinguish between spaces, uppercase and lowercase.%>", "");
|
||||
if (key) {
|
||||
window.location.href = '<%=api.url("autoswitch_add_node")%>' + "?key=" + key;
|
||||
}
|
||||
}
|
||||
function remove_node_by_key() {
|
||||
var key = prompt("<%:Please enter the node keyword, pay attention to distinguish between spaces, uppercase and lowercase.%>", "");
|
||||
if (key) {
|
||||
window.location.href = '<%=api.url("autoswitch_remove_node")%>' + "?key=" + key;
|
||||
}
|
||||
}
|
||||
//]]>
|
||||
</script>
|
||||
<input class="btn cbi-button cbi-button-add" type="button" onclick="add_node_by_key()" value="<%:Add nodes to the standby node list by keywords%>" />
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" onclick="remove_node_by_key()" value="<%:Delete nodes in the standby node list by keywords%>" />
|
41
luci-app-passwall2/luasrc/view/passwall2/global/faq.htm
Normal file
41
luci-app-passwall2/luasrc/view/passwall2/global/faq.htm
Normal file
@ -0,0 +1,41 @@
|
||||
<%
|
||||
local api = require "luci.passwall2.api"
|
||||
-%>
|
||||
<div class="cbi-section cbi-tblsection">
|
||||
<div id="dns_div">
|
||||
<ul><b style="color:red"><%:About DNS issues:%></b>
|
||||
<li style="color:red">1. <span><%:Some browsers may have built-in DNS, be sure to close. Example: Chrome. Settings - Security and Privacy - Security - Use secure DNS disabled.%></span></li>
|
||||
<li style="color:red">2. <span><%:Sometimes after restart, you can not internet. At this time, close all browsers (important), Windows Client, please `ipconfig /flushdns`. Please close the WiFi on the phone, cut the flight mode and then cut back.%></span></li>
|
||||
<li style="color:red">3. <span><%:The client DNS and the default gateway must point to this router.%></span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="div2"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var origin = window.location.origin;
|
||||
var reset_url = origin + "<%=api.url("reset_config")%>";
|
||||
var hide_url = origin + "<%=api.url("hide")%>";
|
||||
var show_url = origin + "<%=api.url("show")%>";
|
||||
|
||||
function reset(url) {
|
||||
if (confirm('<%:Are you sure to reset?%>') == true) {
|
||||
window.location.href = reset_url;
|
||||
}
|
||||
}
|
||||
|
||||
function hide(url) {
|
||||
if (confirm('<%:Are you sure to hide?%>') == true) {
|
||||
window.location.href = hide_url;
|
||||
}
|
||||
}
|
||||
|
||||
var dom = document.getElementById("div2");
|
||||
if (dom) {
|
||||
var li = "";
|
||||
li += "<%:Restore the default configuration method. Input example in the address bar:%>" + "<a href='#' onclick='reset()'>" + reset_url + "</a>" + "<br />";
|
||||
li += "<%:Hide menu method, input example in the address bar:%>" + "<a href='#' onclick='hide()'>" + hide_url + "</a>" + "<br />";
|
||||
li += "<%:After the hidden to the display, input example in the address bar:%>" + "<a href='#'>" + show_url + "</a>" + "<br />";
|
||||
dom.innerHTML = li;
|
||||
}
|
||||
</script>
|
127
luci-app-passwall2/luasrc/view/passwall2/global/footer.htm
Normal file
127
luci-app-passwall2/luasrc/view/passwall2/global/footer.htm
Normal file
@ -0,0 +1,127 @@
|
||||
<%
|
||||
local api = require "luci.passwall2.api"
|
||||
local auto_switch = api.uci_get_type("auto_switch", "enable", 0)
|
||||
-%>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
function go() {
|
||||
var _status = document.getElementsByClassName('_status');
|
||||
for (var i = 0; i < _status.length; i++) {
|
||||
var id = _status[i].getAttribute("socks_id");
|
||||
XHR.get('<%=api.url("socks_status")%>', {
|
||||
index: i,
|
||||
id: id
|
||||
},
|
||||
function(x, result) {
|
||||
var index = result.index;
|
||||
var div = '';
|
||||
var div1 = '<font style="font-weight:bold;" color="green">✓</font> ';
|
||||
var div2 = '<font style="font-weight:bold;" color="red">X</font> ';
|
||||
|
||||
if (result.socks_status) {
|
||||
div += div1;
|
||||
} else {
|
||||
div += div2;
|
||||
}
|
||||
if (result.use_http) {
|
||||
if (result.http_status) {
|
||||
div += div1;
|
||||
} else {
|
||||
div += div2;
|
||||
}
|
||||
}
|
||||
_status[index].innerHTML = div;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
var global_id = null;
|
||||
var global = document.getElementById("cbi-passwall2-global");
|
||||
if (global) {
|
||||
var node = global.getElementsByClassName("cbi-section-node")[0];
|
||||
var node_id = node.getAttribute("id");
|
||||
global_id = node_id;
|
||||
var reg1 = new RegExp("(?<=" + node_id + "-).*?(?=(node))")
|
||||
for (var i = 0; i < node.childNodes.length; i++) {
|
||||
if (node.childNodes[i].childNodes && node.childNodes[i].childNodes.length > 0) {
|
||||
for (var k = 0; k < node.childNodes[i].childNodes.length; k++) {
|
||||
try {
|
||||
var dom = node.childNodes[i].childNodes[k];
|
||||
if (dom.id) {
|
||||
var s = dom.id.match(reg1);
|
||||
if (s) {
|
||||
var cbi_id = global_id + "-"
|
||||
var dom_id = dom.id.split(cbi_id).join(cbi_id.split("-").join(".")).split("cbi.").join("cbid.")
|
||||
var node_select = document.getElementsByName(dom_id)[0];
|
||||
var node_select_value = node_select.value;
|
||||
if (node_select_value && node_select_value != "nil" && node_select_value.indexOf("socks://") != 0 && node_select_value.indexOf("_default") != 0 && node_select_value.indexOf("_direct") != 0 && node_select_value.indexOf("_blackhole") != 0) {
|
||||
if (node_select.tagName == "INPUT") {
|
||||
node_select = document.getElementById("cbi.combobox." + dom_id);
|
||||
}
|
||||
|
||||
var new_a = document.createElement("a");
|
||||
new_a.innerHTML = "<%:Edit%>";
|
||||
new_a.href = "#";
|
||||
new_a.setAttribute("onclick", "location.href='" + '<%=api.url("node_config")%>' + "/" + node_select_value + "'");
|
||||
var new_html = new_a.outerHTML;
|
||||
|
||||
if (s[0] == "") {
|
||||
var log_a = document.createElement("a");
|
||||
log_a.innerHTML = "<%:Log%>";
|
||||
log_a.href = "#";
|
||||
log_a.setAttribute("onclick", "window.open('" + '<%=api.url("get_redir_log")%>' + "?id=" + "global" + "', '_blank')");
|
||||
new_html += "  " + log_a.outerHTML;
|
||||
}
|
||||
|
||||
node_select.insertAdjacentHTML("afterend", "  " + new_html);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var socks = document.getElementById("cbi-passwall2-socks");
|
||||
if (socks) {
|
||||
var socks_enabled_dom = document.getElementById(global_id + "-socks_enabled");
|
||||
socks_enabled_dom.parentNode.removeChild(socks_enabled_dom);
|
||||
var descr = socks.getElementsByClassName("cbi-section-descr")[0];
|
||||
descr.outerHTML = socks_enabled_dom.outerHTML;
|
||||
rows = socks.getElementsByClassName("cbi-section-table-row");
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
try {
|
||||
var row = rows[i];
|
||||
var id = row.id;
|
||||
if (!id) continue;
|
||||
var dom_id = id + "-node";
|
||||
var node = document.getElementById(dom_id);
|
||||
var dom_id = dom_id.replace("cbi-", "cbid-").replace(new RegExp("-", 'g'), ".");
|
||||
var node_select = document.getElementsByName(dom_id)[0];
|
||||
var node_select_value = node_select.value;
|
||||
if (node_select_value && node_select_value != "nil") {
|
||||
var v = document.getElementById(dom_id + "-" + node_select_value);
|
||||
if (v) {
|
||||
node_select.title = v.text;
|
||||
} else {
|
||||
node_select.title = node_select.options[node_select.options.selectedIndex].text;
|
||||
}
|
||||
|
||||
var new_a = document.createElement("a");
|
||||
new_a.innerHTML = "<%:Edit%>";
|
||||
new_a.href = "#";
|
||||
new_a.setAttribute("onclick","location.href='" + '<%=api.url("node_config")%>' + "/" + node_select_value + "'");
|
||||
|
||||
node_select.insertAdjacentHTML("afterend", "  " + new_a.outerHTML);
|
||||
}
|
||||
} catch(err) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
setTimeout("go()", 1000);
|
||||
|
||||
//]]>
|
||||
</script>
|
203
luci-app-passwall2/luasrc/view/passwall2/global/status.htm
Normal file
203
luci-app-passwall2/luasrc/view/passwall2/global/status.htm
Normal file
File diff suppressed because one or more lines are too long
26
luci-app-passwall2/luasrc/view/passwall2/haproxy/status.htm
Normal file
26
luci-app-passwall2/luasrc/view/passwall2/haproxy/status.htm
Normal file
@ -0,0 +1,26 @@
|
||||
<%
|
||||
local api = require "luci.passwall2.api"
|
||||
local console_port = api.uci_get_type("global_haproxy", "console_port", "")
|
||||
-%>
|
||||
<p id="_status"></p>
|
||||
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
XHR.poll(3, '<%=api.url("haproxy_status")%>', null,
|
||||
function(x, result) {
|
||||
if (x && x.status == 200) {
|
||||
var _status = document.getElementById('_status');
|
||||
if (_status) {
|
||||
if (result) {
|
||||
_status.innerHTML = '<input type="button" class="btn cbi-button cbi-button-apply" value="<%:Enter interface%>" onclick="openwebui()" />';
|
||||
} else {
|
||||
_status.innerHTML = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function openwebui(){
|
||||
var url = window.location.hostname + ":<%=console_port%>";
|
||||
window.open('http://' + url, 'target', '');
|
||||
}
|
||||
//]]></script>
|
30
luci-app-passwall2/luasrc/view/passwall2/log/log.htm
Normal file
30
luci-app-passwall2/luasrc/view/passwall2/log/log.htm
Normal file
@ -0,0 +1,30 @@
|
||||
<%
|
||||
local api = require "luci.passwall2.api"
|
||||
-%>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
function clearlog(btn) {
|
||||
XHR.get('<%=api.url("clear_log")%>', null,
|
||||
function(x, data) {
|
||||
if(x && x.status == 200) {
|
||||
var log_textarea = document.getElementById('log_textarea');
|
||||
log_textarea.innerHTML = "";
|
||||
log_textarea.scrollTop = log_textarea.scrollHeight;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
XHR.poll(5, '<%=api.url("get_log")%>', null,
|
||||
function(x, data) {
|
||||
if(x && x.status == 200) {
|
||||
var log_textarea = document.getElementById('log_textarea');
|
||||
log_textarea.innerHTML = x.responseText;
|
||||
}
|
||||
}
|
||||
);
|
||||
//]]>
|
||||
</script>
|
||||
<fieldset class="cbi-section" id="_log_fieldset">
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" onclick="clearlog()" value="<%:Clear logs%>" />
|
||||
<textarea id="log_textarea" class="cbi-input-textarea" style="width: 100%;margin-top: 10px;" data-update="change" rows="40" wrap="off" readonly="readonly"></textarea>
|
||||
</fieldset>
|
@ -0,0 +1,108 @@
|
||||
<%
|
||||
local api = require "luci.passwall2.api"
|
||||
-%>
|
||||
|
||||
<style>
|
||||
#add_link_div{
|
||||
display: none;
|
||||
width: auto;
|
||||
position: absolute;
|
||||
left:50%;
|
||||
top:50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 99;
|
||||
text-align: center;
|
||||
background: white;
|
||||
box-shadow: darkgrey 10px 10px 30px 5px;
|
||||
padding: 30px 15px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
function ajax_add_node(link) {
|
||||
if (link) {
|
||||
XHR.get('<%=api.url("link_add_node")%>', {
|
||||
'link': link
|
||||
},
|
||||
function(x, data) {
|
||||
if(x && x.status == 200) {
|
||||
window.location.href = '<%=api.url("node_list")%>';
|
||||
}
|
||||
else {
|
||||
alert("<%:Error%>");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function open_add_link_div() {
|
||||
document.getElementById("add_link_div").style.display = "block";
|
||||
document.getElementById("nodes_link").focus();
|
||||
}
|
||||
|
||||
function close_add_link_div() {
|
||||
document.getElementById("add_link_div").style.display = "none";
|
||||
}
|
||||
|
||||
function add_node() {
|
||||
var nodes_link = document.getElementById("nodes_link").value;
|
||||
if (nodes_link.trim() != "") {
|
||||
var supports = "ss ssr vmess vless trojan hysteria";
|
||||
var itype = nodes_link.split('://')[0];
|
||||
if (itype.trim() != "" && supports.indexOf(itype) >= 0) {
|
||||
ajax_add_node(nodes_link);
|
||||
}
|
||||
else {
|
||||
alert("<%:Please enter the correct link.%>");
|
||||
}
|
||||
}
|
||||
else {
|
||||
document.getElementById("nodes_link").focus();
|
||||
}
|
||||
}
|
||||
|
||||
function clear_all_nodes() {
|
||||
if (confirm('<%:Are you sure to clear all nodes?%>') == true){
|
||||
XHR.get('<%=api.url("clear_all_nodes")%>', null,
|
||||
function(x, data) {
|
||||
if(x && x.status == 200) {
|
||||
window.location.href = '<%=api.url("node_list")%>';
|
||||
}
|
||||
else {
|
||||
alert("<%:Error%>");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//]]>
|
||||
</script>
|
||||
|
||||
<div id="add_link_div">
|
||||
<div class="cbi-value">
|
||||
<label class="cbi-value-title"><%:SS/SSR/Vmess/VLESS/Trojan/Hysteria Link%></label>
|
||||
<div class="cbi-value-field">
|
||||
<textarea id="nodes_link" rows="5" cols="50"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cbi-value">
|
||||
<div class="cbi-value-field" style="display: unset">
|
||||
<input class="btn cbi-button cbi-button-add" type="button" onclick="add_node()" value="<%:Add%>" />
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" onclick="close_add_link_div()" value="<%:Close%>" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="cbi-value">
|
||||
<label class="cbi-value-title"></label>
|
||||
<div class="cbi-value-field">
|
||||
<input class="btn cbi-button cbi-button-add" type="submit" name="cbi.cts.<%=api.appname%>.nodes." value="<%:Add%>" />
|
||||
<input class="btn cbi-button cbi-button-add" type="button" onclick="open_add_link_div()" value="<%:Add the node via the link%>" />
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" onclick="clear_all_nodes()" value="<%:Clear all nodes%>" />
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" onclick="delete_select_nodes()" value="<%:Delete select nodes%>" />
|
||||
<input class="btn cbi-button" type="button" onclick="checked_all_node(this)" value="<%:Select all%>" />
|
||||
<input class="btn cbi-button cbi-button-apply" type="submit" name="cbi.apply" value="<%:Save & Apply%>" />
|
||||
<div id="div_node_count"></div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,851 @@
|
||||
<%+cbi/valueheader%>
|
||||
<%
|
||||
local api = require "luci.passwall2.api"
|
||||
local has_v2ray = api.is_finded("v2ray")
|
||||
local has_xray = api.is_finded("xray")
|
||||
-%>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
function padright(str, cnt, pad) {
|
||||
return str + Array(cnt + 1).join(pad);
|
||||
}
|
||||
|
||||
function b64EncodeUnicode(str) {
|
||||
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
|
||||
return String.fromCharCode('0x' + p1);
|
||||
}));
|
||||
}
|
||||
|
||||
function b64encutf8safe(str) {
|
||||
return b64EncodeUnicode(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, '');
|
||||
}
|
||||
|
||||
function b64DecodeUnicode(str) {
|
||||
return decodeURIComponent(Array.prototype.map.call(atob(str), function(c) {
|
||||
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
||||
}).join(''));
|
||||
}
|
||||
|
||||
function b64decutf8safe(str) {
|
||||
var l;
|
||||
str = str.replace(/-/g, "+").replace(/_/g, "/");
|
||||
l = str.length;
|
||||
l = (4 - l % 4) % 4;
|
||||
if (l)
|
||||
str = padright(str, l, "=");
|
||||
return b64DecodeUnicode(str);
|
||||
}
|
||||
|
||||
function b64encsafe(str) {
|
||||
return btoa(str).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, '')
|
||||
}
|
||||
|
||||
function b64decsafe(str) {
|
||||
var l;
|
||||
str = str.replace(/-/g, "+").replace(/_/g, "/");
|
||||
l = str.length;
|
||||
l = (4 - l % 4) % 4;
|
||||
if (l)
|
||||
str = padright(str, l, "=");
|
||||
return atob(str);
|
||||
}
|
||||
|
||||
function dictvalue(d, key) {
|
||||
var v = d[key];
|
||||
if (typeof(v) === 'undefined' || v === '')
|
||||
return '';
|
||||
return b64decsafe(v);
|
||||
}
|
||||
function parseNodeUrl(url) {
|
||||
let protocol = url.substring(0, url.indexOf("://")) + ":"
|
||||
let str = "http" + url.substring(url.indexOf("://"))
|
||||
const parsedUrl = new URL(str);
|
||||
var r = {
|
||||
hash: parsedUrl.hash, // #asd
|
||||
host: parsedUrl.host, // localhost:257
|
||||
hostname: parsedUrl.hostname, // localhost
|
||||
port: parsedUrl.port, // 257
|
||||
search: parsedUrl.search, // ?asd=asd
|
||||
passwd: parsedUrl.username || parsedUrl.password // username
|
||||
};
|
||||
return r;
|
||||
}
|
||||
|
||||
function buildUrl(btn, urlname, sid) {
|
||||
var opt = {
|
||||
base: "cbid.passwall2",
|
||||
client : true,
|
||||
get: function(opt) {
|
||||
var id = this.base + "." + opt;
|
||||
var obj = document.getElementsByName(id)[0] || document.getElementsByClassName(id)[0] || document.getElementById(id)
|
||||
if (obj) {
|
||||
return obj;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
getlist: function(opt) {
|
||||
var id = this.base + "." + opt;
|
||||
var objs = document.getElementsByName(id) || document.getElementsByClassName(id);
|
||||
var ret = [];
|
||||
if (objs) {
|
||||
for (var i = 0; i < objs.length; i++) {
|
||||
ret[i] = objs[i].value;
|
||||
}
|
||||
} else {
|
||||
alert("<%:Faltal on get option, please help in debug: %>" + opt);
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
query: function(param, src, default_value, tval = "1", fval = "0") {
|
||||
var ret = "&" + param + "=";
|
||||
var obj = this.get(src);
|
||||
if (obj) {
|
||||
if (obj.type === "checkbox") {
|
||||
return ret + (obj.checked === true ? tval : fval);
|
||||
} else {
|
||||
var result = encodeURIComponent(obj.value);
|
||||
if ((result == null || result.trim() == "") && default_value)
|
||||
result = default_value;
|
||||
return ret + result;
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
var s = document.getElementById(urlname + "-status");
|
||||
if (!s) {
|
||||
alert("Never");
|
||||
return false;
|
||||
}
|
||||
opt.base = "cbid." + urlname + "." + sid;
|
||||
opt.client = urlname.indexOf("server") === -1;
|
||||
var v_type = opt.get("type").value;
|
||||
var v_alias = opt.get("remarks");
|
||||
var _address = ""
|
||||
try {
|
||||
var v_server = opt.get("address");
|
||||
const ipv6Regex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/;
|
||||
if (ipv6Regex.test(v_server.value)) {
|
||||
_address = "[" + v_server.value + "]"
|
||||
} else {
|
||||
_address = v_server.value
|
||||
}
|
||||
} catch(e) {
|
||||
}
|
||||
var url = null;
|
||||
if (v_type === "SS") {
|
||||
var v_port = opt.get("port");
|
||||
var v_method = opt.get("ss_encrypt_method");
|
||||
var v_password = opt.get("password");
|
||||
|
||||
url = b64encsafe(v_method.value + ":" + v_password.value) + "@" +
|
||||
_address + ":" +
|
||||
v_port.value + "/?";
|
||||
|
||||
var params = "";
|
||||
var v_plugin = opt.get("ss_plugin").value;
|
||||
if (v_plugin && v_plugin != "none") {
|
||||
if (v_plugin == "simple-obfs" || v_plugin == "obfs-local") {
|
||||
v_plugin = "obfs-local";
|
||||
}
|
||||
var v_plugin_opts = opt.get("ss_plugin_opts").value;
|
||||
if (v_plugin_opts && v_plugin_opts != "") {
|
||||
v_plugin += encodeURI(";" + v_plugin_opts);
|
||||
}
|
||||
params += "&plugin=" + encodeURI(v_plugin);
|
||||
}
|
||||
params += "&group="
|
||||
params += "#" + encodeURI(v_alias.value);
|
||||
if (params[0] == "&") {
|
||||
params = params.substring(1);
|
||||
}
|
||||
url += params;
|
||||
} else if (v_type === "SSR") {
|
||||
var v_port = opt.get("port");
|
||||
var v_protocol = opt.get("ssr_protocol");
|
||||
var v_method = opt.get("ssr_encrypt_method");
|
||||
var v_obfs = opt.get("obfs");
|
||||
var v_password = opt.get("password");
|
||||
var v_obfs_param = opt.get("obfs_param");
|
||||
var v_protocol_param = opt.get("protocol_param");
|
||||
var ssr_str = _address + ":" +
|
||||
v_port.value + ":" +
|
||||
v_protocol.value + ":" +
|
||||
v_method.value + ":" +
|
||||
v_obfs.value + ":" +
|
||||
b64encsafe(v_password.value) +
|
||||
"/?obfsparam=" + b64encsafe(v_obfs_param.value) +
|
||||
"&protoparam=" + b64encsafe(v_protocol_param.value) +
|
||||
"&remarks=" + b64encutf8safe(v_alias.value);
|
||||
url = b64encsafe(ssr_str);
|
||||
} else if ((v_type === "V2ray" || v_type === "Xray") && opt.get("protocol").value === "vmess") {
|
||||
v_type = "vmess";
|
||||
var info = {};
|
||||
info.v = "2";
|
||||
info.ps = v_alias.value;
|
||||
info.add = opt.get("address").value;
|
||||
//info.add = _address;
|
||||
info.port = opt.get("port").value;
|
||||
info.id = opt.get("uuid").value;
|
||||
|
||||
var v_transport = opt.get("transport").value;
|
||||
if (v_transport === "ws") {
|
||||
info.host = opt.get("ws_host").value;
|
||||
info.path = opt.get("ws_path").value;
|
||||
} else if (v_transport === "h2") {
|
||||
info.host = opt.get("h2_host").value;
|
||||
info.path = opt.get("h2_path").value;
|
||||
} else if (v_transport === "tcp") {
|
||||
info.type = opt.get("tcp_guise").value;
|
||||
if (info.type === "http") {
|
||||
info.host = opt.get("tcp_guise_http_host").value;
|
||||
info.path = opt.get("tcp_guise_http_path").value;
|
||||
}
|
||||
} else if (v_transport === "mkcp") {
|
||||
v_transport = "kcp";
|
||||
info.type = opt.get("mkcp_guise").value;
|
||||
} else if (v_transport === "quic") {
|
||||
info.type = opt.get("quic_guise").value;
|
||||
info.key = opt.get("quic_key").value;
|
||||
info.securty = opt.get("quic_security").value;
|
||||
} else if (v_transport === "grpc") {
|
||||
info.path = opt.get("grpc_serviceName").value;
|
||||
}
|
||||
if (info.path && info.path != "") {
|
||||
info.path = encodeURI(info.path);
|
||||
}
|
||||
info.net = v_transport;
|
||||
|
||||
info.security = opt.get("security").value || "auto";
|
||||
if (opt.get("tls").checked) {
|
||||
var v_security = "tls";
|
||||
info.tls = "tls";
|
||||
info.sni = opt.get("tls_serverName").value;
|
||||
}
|
||||
url = b64EncodeUnicode(JSON.stringify(info));
|
||||
} else if ((v_type === "V2ray" || v_type === "Xray") && opt.get("protocol").value === "vless") {
|
||||
v_type = "vless";
|
||||
var v_password = opt.get("uuid");
|
||||
var v_port = opt.get("port");
|
||||
url = encodeURIComponent(v_password.value) +
|
||||
"@" + _address +
|
||||
":" + v_port.value + "?";
|
||||
|
||||
var params = "";
|
||||
var v_transport = opt.get("transport").value;
|
||||
if (v_transport === "ws") {
|
||||
params += opt.query("host", "ws_host");
|
||||
params += opt.query("path", "ws_path");
|
||||
} else if (v_transport === "h2") {
|
||||
v_transport = "http";
|
||||
params += opt.query("host", "h2_host");
|
||||
params += opt.query("path", "h2_path");
|
||||
} else if (v_transport === "tcp") {
|
||||
params += opt.query("headerType", "tcp_guise");
|
||||
params += opt.query("host", "tcp_guise_http_host");
|
||||
params += opt.query("path", "tcp_guise_http_path");
|
||||
} else if (v_transport === "mkcp") {
|
||||
v_transport = "kcp";
|
||||
params += opt.query("headerType", "mkcp_guise");
|
||||
} else if (v_transport === "quic") {
|
||||
params += opt.query("headerType", "quic_guise");
|
||||
params += opt.query("key", "quic_key");
|
||||
params += opt.query("quicSecurity", "quic_security");
|
||||
} else if (v_transport === "grpc") {
|
||||
//不知道是用path还是serviceName,这里先这样吧
|
||||
params += opt.query("path", "grpc_serviceName");
|
||||
params += opt.query("serviceName", "grpc_serviceName");
|
||||
params += opt.query("mode", "grpc_mode");
|
||||
}
|
||||
params += "&type=" + v_transport;
|
||||
|
||||
params += opt.query("encryption", "encryption");
|
||||
if (opt.get("tls").checked) {
|
||||
var v_security = "tls";
|
||||
if (opt.get("xray_fingerprint") && opt.get("xray_fingerprint").value != "") {
|
||||
let v_fp = opt.get("xray_fingerprint").value;
|
||||
params += "&fp=" + v_fp;
|
||||
}
|
||||
if(opt.get("reality") && opt.get("reality").checked) {
|
||||
v_security = "reality";
|
||||
if (opt.get("reality_fingerprint") && opt.get("reality_fingerprint").value != "") {
|
||||
let v_fp = opt.get("reality_fingerprint").value;
|
||||
params += "&fp=" + v_fp;
|
||||
}
|
||||
params += opt.query("pbk", "reality_publicKey");
|
||||
params += opt.query("sid", "reality_shortId");
|
||||
params += opt.query("spx", "reality_spiderX");
|
||||
}
|
||||
if (opt.get("tlsflow") && opt.get("tlsflow").value) {
|
||||
let v_flow = opt.get("tlsflow").value;
|
||||
params += "&flow=" + v_flow;
|
||||
}
|
||||
params += "&security=" + v_security;
|
||||
params += opt.query("sni", "tls_serverName");
|
||||
}
|
||||
|
||||
params += "#" + encodeURI(v_alias.value);
|
||||
if (params[0] == "&") {
|
||||
params = params.substring(1);
|
||||
}
|
||||
url += params;
|
||||
} else if (((v_type === "V2ray" || v_type === "Xray") && opt.get("protocol").value === "trojan")) {
|
||||
if (((v_type === "V2ray" || v_type === "Xray") && opt.get("protocol").value === "trojan")) {
|
||||
v_type = "trojan";
|
||||
}
|
||||
var v_password = opt.get("password");
|
||||
var v_port = opt.get("port");
|
||||
url = encodeURIComponent(v_password.value) +
|
||||
"@" + _address +
|
||||
":" + v_port.value + "/?";
|
||||
var params = "";
|
||||
if (opt.get("tls").checked) {
|
||||
params += opt.query("sni", "tls_serverName");
|
||||
params += "&tls=1"
|
||||
params += opt.query("allowinsecure", "tls_allowInsecure");
|
||||
}
|
||||
params += "#" + encodeURI(v_alias.value);
|
||||
if (params[0] == "&") {
|
||||
params = params.substring(1);
|
||||
}
|
||||
url += params;
|
||||
} else if (v_type === "Brook") {
|
||||
var url = "";
|
||||
var params = "?";
|
||||
var v_protocol = opt.get("brook_protocol");
|
||||
var v_port = opt.get("port");
|
||||
var v_password = opt.get("password");
|
||||
var b_protocol_value = v_protocol.value.split('client').join('server');
|
||||
|
||||
var url_protocol = b_protocol_value;
|
||||
params += opt.query("password", "password");
|
||||
if (b_protocol_value == "wsserver") {
|
||||
var server = '';
|
||||
var prefix = "ws://";
|
||||
if (opt.get("brook_tls").checked) {
|
||||
prefix = "wss://";
|
||||
url_protocol = 'wssserver';
|
||||
}
|
||||
var v_path = opt.get("ws_path");
|
||||
var v_path_value = v_path.value || '/ws';
|
||||
if (v_path_value.length > 1 && v_path_value.indexOf('/') < 0) {
|
||||
v_path_value = '/' + v_path_value;
|
||||
}
|
||||
params += "&" + url_protocol + "=" + encodeURIComponent(prefix + _address + ":" + v_port.value + v_path_value);
|
||||
} else {
|
||||
params += "&" + url_protocol + "=" + encodeURIComponent(_address + ":" + v_port.value);
|
||||
}
|
||||
url += url_protocol;
|
||||
url += params;
|
||||
} else if (v_type === "Hysteria") {
|
||||
var v_port = opt.get("port");
|
||||
var params = "";
|
||||
params += opt.query("protocol", "hysteria_protocol");
|
||||
params += opt.query("auth", "hysteria_auth_password");
|
||||
params += opt.query("peer", "tls_serverName");
|
||||
params += opt.query("insecure", "tls_allowInsecure");
|
||||
params += opt.query("upmbps", "hysteria_up_mbps", 1000);
|
||||
params += opt.query("downmbps", "hysteria_down_mbps", 1000);
|
||||
params += opt.query("alpn", "hysteria_alpn");
|
||||
params += opt.query("obfsParam", "hysteria_obfs");
|
||||
var url =
|
||||
_address + ":" +
|
||||
v_port.value + "?" +
|
||||
params +
|
||||
"#" + encodeURI(v_alias.value);
|
||||
}
|
||||
if (url) {
|
||||
url = v_type.toLowerCase() + "://" + url;
|
||||
var textarea = document.createElement("textarea");
|
||||
textarea.textContent = url;
|
||||
textarea.style.position = "fixed";
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
try {
|
||||
document.execCommand("copy"); // Security exception may be thrown by some browsers.
|
||||
s.innerHTML = "<font color='green'><%:Share URL to clipboard successfully.%></font>";
|
||||
} catch (ex) {
|
||||
s.innerHTML = "<font color='red'><%:Share URL to clipboard unable.%></font>";
|
||||
} finally {
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
//alert(url);
|
||||
} else {
|
||||
alert("<%:Not a supported scheme:%> " + v_type);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function fromUrl(btn, urlname, sid) {
|
||||
var opt = {
|
||||
base: 'cbid.passwall2',
|
||||
client : true,
|
||||
get: function(opt) {
|
||||
var obj;
|
||||
var id = this.base + '.' + opt;
|
||||
obj = document.getElementsByName(id)[0] || document.getElementById(id);
|
||||
if (obj) {
|
||||
var combobox = document.getElementById('cbi.combobox.' + id);
|
||||
if (combobox) {
|
||||
obj.combobox = combobox;
|
||||
}
|
||||
var div = document.getElementById(id);
|
||||
if (div && div.getElementsByTagName("li").length > 0) {
|
||||
obj = div;
|
||||
}
|
||||
return obj;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
set: function(opt, val) {
|
||||
var obj;
|
||||
obj = this.get(opt);
|
||||
if (obj) {
|
||||
var event = document.createEvent("HTMLEvents");
|
||||
event.initEvent("change", true, true);
|
||||
if (obj.type === 'checkbox') {
|
||||
obj.checked = val;
|
||||
} else {
|
||||
obj.value = val;
|
||||
if (obj.combobox) {
|
||||
obj.combobox.value = val;
|
||||
}
|
||||
|
||||
var list = obj.getElementsByTagName("li");
|
||||
if (list.length > 0) {
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
var li = list[i];
|
||||
var data = li.getAttribute("data-value");
|
||||
li.removeAttribute("selected");
|
||||
li.removeAttribute("display");
|
||||
if (data && data == val) {
|
||||
li.setAttribute("selected", true);
|
||||
li.setAttribute("display", "0");
|
||||
}
|
||||
}
|
||||
var input = document.getElementsByName(obj.id)[0];
|
||||
if (input) {
|
||||
input.value = val;
|
||||
} else {
|
||||
var input = document.createElement("input");
|
||||
input.setAttribute("type", "hidden") ;
|
||||
input.setAttribute("name", obj.id) ;
|
||||
input.setAttribute("value", val) ;
|
||||
obj.appendChild(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
obj.dispatchEvent(event);
|
||||
} catch(err) {
|
||||
}
|
||||
} else {
|
||||
//alert('<%:Faltal on set option, please help in debug: %>' + opt + ' = ' + val);
|
||||
}
|
||||
},
|
||||
setlist: function(opt, vlist) {
|
||||
var id = this.base + "." + opt;
|
||||
var objs = document.getElementsByName(id) || document.getElementsByClassName(id);
|
||||
if (objs) {
|
||||
var values = "";
|
||||
for (var i = 0; i < vlist.length; i++) {
|
||||
values += vlist[i] + ", ";
|
||||
}
|
||||
alert("Manually input the option:\n" + opt + "s:\n[" + values + "]");
|
||||
} else {
|
||||
//alert("<%:Faltal on set option, please help in debug: %>" + opt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var s = document.getElementById(urlname + '-status');
|
||||
if (!s) {
|
||||
alert("Never");
|
||||
return false;
|
||||
}
|
||||
opt.base = "cbid." + urlname + "." + sid;
|
||||
opt.client = urlname.indexOf("server") === -1;
|
||||
var ssrurl = prompt('<%:Paste Share URL Here%>', '');
|
||||
if (ssrurl === null || ssrurl === "") {
|
||||
return false;
|
||||
}
|
||||
s.innerHTML = "";
|
||||
var ssu = ssrurl.split('://');
|
||||
var event = document.createEvent("HTMLEvents");
|
||||
event.initEvent("change", true, true);
|
||||
if (ssu[0] === "ssr") {
|
||||
//var b64c = ssu[1].match(/([A-Za-z0-9_-]+)/);
|
||||
var sstr = b64decsafe(ssu[1]);
|
||||
var ploc = sstr.indexOf("/?");
|
||||
var url0 = "", param = "";
|
||||
if (ploc > 0) {
|
||||
url0 = sstr.substr(0, ploc);
|
||||
param = sstr.substr(ploc + 2);
|
||||
} else {
|
||||
var url0 = sstr;
|
||||
}
|
||||
var ssm = url0.match(/^(.+):([^:]+):([^:]*):([^:]+):([^:]*):([^:]+)/);
|
||||
if (!ssm || ssm.length < 7) {
|
||||
s.innerHTML = "<font color='red'><%:Invalid Share URL Format%></font>";
|
||||
return false;
|
||||
}
|
||||
var pdict = {};
|
||||
if (param.length > 2) {
|
||||
var a = param.split('&');
|
||||
for (var i = 0; i < a.length; i++) {
|
||||
var b = a[i].split('=');
|
||||
pdict[decodeURIComponent(b[0])] = decodeURIComponent(b[1] || '');
|
||||
}
|
||||
}
|
||||
opt.set('type', "SSR");
|
||||
opt.set('address', ssm[1]);
|
||||
opt.set('port', ssm[2]);
|
||||
opt.set('ssr_protocol', ssm[3]);
|
||||
opt.set('ssr_encrypt_method', ssm[4]);
|
||||
opt.set('obfs', ssm[5]);
|
||||
opt.set('password', b64decsafe(ssm[6]));
|
||||
opt.set('obfs_param', dictvalue(pdict, 'obfsparam'));
|
||||
opt.set('protocol_param', dictvalue(pdict, 'protoparam'));
|
||||
var rem = pdict['remarks'];
|
||||
if (typeof(rem) !== 'undefined' && rem !== '' && rem.length > 0)
|
||||
opt.set('remarks', b64decutf8safe(rem));
|
||||
} else if (ssu[0] === "ss") {
|
||||
var url0 = "", param = "";
|
||||
var sipIndex = ssu[1].indexOf("@");
|
||||
var ploc = ssu[1].indexOf("#");
|
||||
if (ploc > 0) {
|
||||
url0 = ssu[1].substr(0, ploc);
|
||||
param = ssu[1].substr(ploc + 1);
|
||||
} else {
|
||||
url0 = ssu[1];
|
||||
}
|
||||
if (sipIndex !== -1) {
|
||||
// SIP002
|
||||
var userInfo = b64decsafe(url0.substr(0, sipIndex));
|
||||
var temp = url0.substr(sipIndex + 1).split("/?");
|
||||
var serverInfo = temp[0].split(":");
|
||||
var server = serverInfo[0];
|
||||
var port = serverInfo[1];
|
||||
var method, password, plugin, pluginOpts;
|
||||
if (temp[1]) {
|
||||
var pluginInfo = decodeURIComponent(temp[1]);
|
||||
var pluginIndex = pluginInfo.indexOf(";");
|
||||
var pluginNameInfo = pluginInfo.substr(0, pluginIndex);
|
||||
plugin = pluginNameInfo.substr(pluginNameInfo.indexOf("=") + 1)
|
||||
pluginOpts = pluginInfo.substr(pluginIndex + 1);
|
||||
}
|
||||
var userInfoSplitIndex = userInfo.indexOf(":");
|
||||
if (userInfoSplitIndex !== -1) {
|
||||
method = userInfo.substr(0, userInfoSplitIndex);
|
||||
password = userInfo.substr(userInfoSplitIndex + 1);
|
||||
}
|
||||
opt.set('type', "SS");
|
||||
opt.set('address', server);
|
||||
opt.set('port', port);
|
||||
opt.set('password', password || "");
|
||||
opt.set('ss_encrypt_method', method || "");
|
||||
opt.set('ss_plugin', plugin || "none");
|
||||
if (plugin && plugin != "none") {
|
||||
opt.set('ss_plugin_opts', pluginOpts || "");
|
||||
}
|
||||
if (param !== undefined) {
|
||||
opt.set('remarks', decodeURI(param));
|
||||
}
|
||||
} else {
|
||||
var sstr = b64decsafe(url0);
|
||||
var team = sstr.split('@');
|
||||
opt.set('type', "SS");
|
||||
var part1 = team[0].split(':');
|
||||
var part2 = team[1].split(':');
|
||||
opt.set('address', part2[0]);
|
||||
opt.set('port', part2[1]);
|
||||
opt.set('password', part1[1]);
|
||||
opt.set('ss_encrypt_method', part1[0]);
|
||||
opt.set('ss_plugin', "none");
|
||||
//opt.set('ss_plugin_opts', "");
|
||||
if (param !== undefined) {
|
||||
opt.set('remarks', decodeURI(param));
|
||||
}
|
||||
}
|
||||
} else if (ssu[0] === "trojan") {
|
||||
var m = parseNodeUrl(ssrurl);
|
||||
var password = m.passwd;
|
||||
if (password === "") {
|
||||
s.innerHTML = "<font color='red'><%:Invalid Share URL Format%></font>";
|
||||
return false;
|
||||
}
|
||||
var queryParam = {};
|
||||
if (m.search.length > 1) {
|
||||
var query = m.search.split('?');
|
||||
var queryParams = query[1];
|
||||
var queryArray = queryParams.split('&');
|
||||
var params;
|
||||
for (i = 0; i < queryArray.length; i++) {
|
||||
params = queryArray[i].split('=');
|
||||
queryParam[decodeURIComponent(params[0]).toLowerCase()] = decodeURIComponent(params[1] || '');
|
||||
}
|
||||
}
|
||||
<% if has_v2ray then %>
|
||||
opt.set('type', "V2ray");
|
||||
<% elseif has_xray then %>
|
||||
opt.set('type', "Xray");
|
||||
<% end %>
|
||||
opt.set('protocol', "trojan");
|
||||
opt.set('address', m.hostname);
|
||||
opt.set('port', m.port || "443");
|
||||
opt.set('password', decodeURIComponent(password));
|
||||
var tls = true;
|
||||
opt.set('tls', tls);
|
||||
if (tls) {
|
||||
opt.set('tls_serverName', queryParam.peer || queryParam.sni || '');
|
||||
opt.set('tls_allowInsecure', queryParam.allowinsecure === '1');
|
||||
}
|
||||
opt.set('mux', queryParam.mux === '1');
|
||||
if (m.hash) {
|
||||
opt.set('remarks', decodeURI(m.hash.substr(1)));
|
||||
}
|
||||
} else if (ssu[0] === "vmess") {
|
||||
var sstr = b64DecodeUnicode(ssu[1]);
|
||||
var ploc = sstr.indexOf("/?");
|
||||
<% if has_v2ray then %>
|
||||
opt.set('type', "V2ray");
|
||||
<% elseif has_xray then %>
|
||||
opt.set('type', "Xray");
|
||||
<% end %>
|
||||
opt.set('protocol', "vmess");
|
||||
var url0, param = "";
|
||||
if (ploc > 0) {
|
||||
url0 = sstr.substr(0, ploc);
|
||||
param = sstr.substr(ploc + 2);
|
||||
}
|
||||
var ssm = JSON.parse(sstr);
|
||||
opt.set('remarks', ssm.ps);
|
||||
opt.set('address', ssm.add);
|
||||
opt.set('port', ssm.port);
|
||||
opt.set('uuid', ssm.id);
|
||||
opt.set('tls', ssm.tls === "tls");
|
||||
if (ssm.tls === "tls") {
|
||||
var tls_serverName = ssm.host;
|
||||
if (ssm.sni) {
|
||||
tls_serverName = ssm.sni
|
||||
}
|
||||
opt.set('tls_serverName', tls_serverName);
|
||||
}
|
||||
ssm.net = ssm.net.toLowerCase();
|
||||
if (ssm.net === "kcp" || ssm.net === "mkcp")
|
||||
ssm.net = "mkcp"
|
||||
opt.set('transport', ssm.net);
|
||||
if (ssm.net === "tcp") {
|
||||
opt.set('tcp_guise', (ssm.host && ssm.path) ? "http" : "none");
|
||||
if (ssm.host && ssm.path) {
|
||||
opt.set('tcp_guise_http_host', ssm.host);
|
||||
opt.set('tcp_guise_http_path', ssm.path);
|
||||
}
|
||||
} else if (ssm.net === "ws") {
|
||||
opt.set('ws_host', ssm.host);
|
||||
opt.set('ws_path', ssm.path);
|
||||
} else if (ssm.net === "h2") {
|
||||
opt.set('h2_host', ssm.host);
|
||||
opt.set('h2_path', ssm.path);
|
||||
} else if (ssm.net === "quic") {
|
||||
opt.set('quic_security', ssm.securty);
|
||||
opt.set('quic_key', ssm.key);
|
||||
} else if (ssm.net === "kcp" || ssm.net === "mkcp") {
|
||||
opt.set('mkcp_guise', ssm.type);
|
||||
} else if (ssm.net === "grpc") {
|
||||
opt.set('grpc_serviceName', ssm.path);
|
||||
}
|
||||
} else if (ssu[0] === "vless") {
|
||||
<% if has_xray then %>
|
||||
opt.set('type', "Xray");
|
||||
<% elseif has_v2ray then %>
|
||||
opt.set('type', "V2ray");
|
||||
<% end %>
|
||||
opt.set('protocol', "vless");
|
||||
var m = parseNodeUrl(ssrurl);
|
||||
var password = m.passwd;
|
||||
if (password === "") {
|
||||
s.innerHTML = "<font color='red'><%:Invalid Share URL Format%></font>";
|
||||
return false;
|
||||
}
|
||||
opt.set('uuid', password);
|
||||
opt.set('address', m.hostname);
|
||||
opt.set('port', m.port || "443");
|
||||
var queryParam = {};
|
||||
if (m.search.length > 1) {
|
||||
var query = m.search.split('?');
|
||||
var queryParams = query[1];
|
||||
var queryArray = queryParams.split('&');
|
||||
var params;
|
||||
for (i = 0; i < queryArray.length; i++) {
|
||||
params = queryArray[i].split('=');
|
||||
queryParam[decodeURIComponent(params[0])] = decodeURIComponent(params[1] || '');
|
||||
}
|
||||
}
|
||||
|
||||
opt.set('encryption', queryParam.encryption);
|
||||
if (queryParam.security) {
|
||||
if (queryParam.security == "tls") {
|
||||
opt.set('tls', true);
|
||||
opt.set('reality', false)
|
||||
opt.set('tlsflow', queryParam.flow || '');
|
||||
opt.set('tls_serverName', queryParam.sni || '');
|
||||
opt.set('tls_allowInsecure', true);
|
||||
if (queryParam.allowinsecure === '0') {
|
||||
opt.set('tls_allowInsecure', false);
|
||||
}
|
||||
if (queryParam.fp && queryParam.fp.trim() != "") {
|
||||
opt.set('xray_fingerprint', queryParam.fp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (queryParam.security == "reality") {
|
||||
opt.set('tls', true);
|
||||
opt.set('reality', true)
|
||||
opt.set('tlsflow', queryParam.flow || '');
|
||||
opt.set('tls_serverName', queryParam.sni || '');
|
||||
if (queryParam.fp && queryParam.fp.trim() != "") {
|
||||
opt.set('reality_fingerprint', queryParam.fp);
|
||||
}
|
||||
opt.set('reality_publicKey', queryParam.pbk || '');
|
||||
opt.set('reality_shortId', queryParam.sid || '');
|
||||
opt.set('reality_spiderX', queryParam.spx || '');
|
||||
}
|
||||
|
||||
queryParam.type = queryParam.type.toLowerCase();
|
||||
if (queryParam.type === "kcp" || queryParam.type === "mkcp")
|
||||
queryParam.type = "mkcp"
|
||||
if (queryParam.type === "h2" || queryParam.type === "http")
|
||||
queryParam.type = "h2"
|
||||
opt.set('transport', queryParam.type);
|
||||
if (queryParam.type === "tcp") {
|
||||
opt.set('tcp_guise', queryParam.headerType || "none");
|
||||
if (queryParam.headerType && queryParam.headerType != "none") {
|
||||
opt.set('tcp_guise_http_host', queryParam.host || "");
|
||||
opt.set('tcp_guise_http_path', queryParam.path || "");
|
||||
}
|
||||
} else if (queryParam.type === "ws") {
|
||||
opt.set('ws_host', queryParam.host || "");
|
||||
opt.set('ws_path', queryParam.path || "");
|
||||
} else if (queryParam.type === "h2" || queryParam.type === "http") {
|
||||
opt.set('h2_host', queryParam.host || "");
|
||||
opt.set('h2_path', queryParam.path || "");
|
||||
} else if (queryParam.type === "quic") {
|
||||
opt.set('quic_guise', queryParam.headerType || "none");
|
||||
opt.set('quic_security', queryParam.quicSecurity);
|
||||
opt.set('quic_key', queryParam.key);
|
||||
} else if (queryParam.type === "kcp" || queryParam.type === "mkcp") {
|
||||
opt.set('mkcp_guise', queryParam.headerType || "none");
|
||||
} else if (queryParam.type === "grpc") {
|
||||
opt.set('grpc_serviceName', (queryParam.serviceName || queryParam.path) || "");
|
||||
opt.set('grpc_mode', queryParam.mode);
|
||||
}
|
||||
|
||||
if (m.hash) {
|
||||
opt.set('remarks', decodeURI(m.hash.substr(1)));
|
||||
}
|
||||
} else if (ssu[0] === "brook") {
|
||||
var stype = "Brook";
|
||||
var m = parseNodeUrl(ssrurl);
|
||||
|
||||
var from_protocol = m.host;
|
||||
var protocol = from_protocol.split('server').join('client');
|
||||
|
||||
var queryParam = {};
|
||||
if (m.search.length > 1) {
|
||||
var query = m.search.split('?');
|
||||
var queryParams = query[1];
|
||||
var queryArray = queryParams.split('&');
|
||||
var params;
|
||||
for (i = 0; i < queryArray.length; i++) {
|
||||
params = queryArray[i].split('=');
|
||||
queryParam[decodeURIComponent(params[0])] = decodeURIComponent(params[1] || '');
|
||||
}
|
||||
}
|
||||
|
||||
var password = queryParam.password;
|
||||
if (password === "") {
|
||||
s.innerHTML = "<font color='red'><%:Invalid Share URL Format%></font>";
|
||||
return false;
|
||||
}
|
||||
|
||||
opt.set('type', stype);
|
||||
opt.set('brook_protocol', protocol);
|
||||
opt.set('password', password);
|
||||
|
||||
if (protocol == 'wsclient' || protocol == 'wssclient') {
|
||||
opt.set('brook_protocol', 'wsclient');
|
||||
var wsserver = queryParam[from_protocol].split('://');
|
||||
wsserver = wsserver[1].split('/');
|
||||
var path = wsserver[1] && '/' + wsserver[1] || '/ws';
|
||||
var server = wsserver[0].split(':');
|
||||
opt.set('address', server[0]);
|
||||
opt.set('port', server[1]);
|
||||
opt.set('ws_path', path);
|
||||
if (protocol == 'wssclient') {
|
||||
opt.set('brook_tls', true);
|
||||
}
|
||||
} else {
|
||||
var server = queryParam[from_protocol].split(':');
|
||||
if (server.length < 2) {
|
||||
s.innerHTML = "<font color='red'><%:Invalid Share URL Format%></font>";
|
||||
return false;
|
||||
}
|
||||
opt.set('address', server[0]);
|
||||
opt.set('port', server[1]);
|
||||
}
|
||||
|
||||
if (m.hash) {
|
||||
opt.set('remarks', decodeURI(m.hash.substr(1)));
|
||||
}
|
||||
} else if (ssu[0] === "hysteria") {
|
||||
var stype = "Hysteria";
|
||||
var m = parseNodeUrl(ssrurl);
|
||||
var queryParam = {};
|
||||
if (m.search.length > 1) {
|
||||
var query = m.search.split('?');
|
||||
var queryParams = query[1];
|
||||
var queryArray = queryParams.split('&');
|
||||
var params;
|
||||
for (i = 0; i < queryArray.length; i++) {
|
||||
params = queryArray[i].split('=');
|
||||
queryParam[decodeURIComponent(params[0])] = decodeURIComponent(params[1] || '');
|
||||
}
|
||||
}
|
||||
opt.set('address', m.hostname);
|
||||
opt.set('port', m.port || "443");
|
||||
opt.set('type', stype);
|
||||
opt.set('hysteria_protocol', queryParam.protocol);
|
||||
opt.set('hysteria_obfs', queryParam.obfsParam);
|
||||
opt.set('hysteria_auth_type', "string");
|
||||
opt.set('hysteria_auth_password', queryParam.auth);
|
||||
opt.set('tls_serverName', queryParam.peer);
|
||||
if (queryParam.insecure && queryParam.insecure == "1") {
|
||||
opt.set('tls_allowInsecure', true);
|
||||
}
|
||||
opt.set('hysteria_alpn', queryParam.alpn);
|
||||
opt.set('hysteria_up_mbps', queryParam.upmbps);
|
||||
opt.set('hysteria_down_mbps', queryParam.downmbps);
|
||||
if (m.hash) {
|
||||
opt.set('remarks', decodeURI(m.hash.substr(1)));
|
||||
}
|
||||
} else {
|
||||
s.innerHTML = "<font color='red'><%:Invalid Share URL Format%></font>: " + ssu[0];
|
||||
return false;
|
||||
}
|
||||
if (opt.get('port') && opt.get('port').value) {
|
||||
opt.get('port').focus();
|
||||
opt.get('port').blur();
|
||||
}
|
||||
s.innerHTML = "<font color='green'><%:Import Finished %></font>";
|
||||
return false;
|
||||
}
|
||||
|
||||
//]]></script>
|
||||
<input type="button" class="btn cbi-button cbi-button-apply" value='<%:From Share URL%>' onclick="return fromUrl(this, '<%=self.option%>', '<%=self.value%>')" />
|
||||
<input type="button" class="btn cbi-button cbi-button-apply" value='<%:Build Share URL%>' onclick="return buildUrl(this, '<%=self.option%>', '<%=self.value%>')" />
|
||||
<span id="<%=self.option%>-status"></span>
|
||||
<%+cbi/valuefooter%>
|
499
luci-app-passwall2/luasrc/view/passwall2/node_list/node_list.htm
Normal file
499
luci-app-passwall2/luasrc/view/passwall2/node_list/node_list.htm
Normal file
@ -0,0 +1,499 @@
|
||||
<%
|
||||
local api = require "luci.passwall2.api"
|
||||
local uci = api.uci
|
||||
|
||||
local default_node_type = ""
|
||||
local shunt_rule_list = {}
|
||||
local node = api.uci_get_type("global", "node", "nil")
|
||||
if node ~= "nil" then
|
||||
local node_type = api.uci_get_type_id(node, "type")
|
||||
local node_protocol = api.uci_get_type_id(node, "protocol")
|
||||
if (node_type == "V2ray" or node_type == "Xray") and node_protocol == "_shunt" then
|
||||
default_node_type = node_protocol
|
||||
uci:foreach("passwall2", "shunt_rules", function(e)
|
||||
if e[".name"] and e.remarks then
|
||||
shunt_rule_list[#shunt_rule_list + 1] = e
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
-%>
|
||||
|
||||
<style>
|
||||
table th, .table .th {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
table td, .table .td {
|
||||
text-align: center;
|
||||
/* white-space: nowrap; */
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
#set_node_div {
|
||||
display: none;
|
||||
width: 30rem;
|
||||
position: fixed;
|
||||
top:50%;
|
||||
padding-top: 30px;
|
||||
z-index: 99;
|
||||
text-align: center;
|
||||
background: white;
|
||||
box-shadow: darkgrey 10px 10px 30px 5px;
|
||||
}
|
||||
|
||||
._now_use {
|
||||
background: #94e1ff !important;
|
||||
}
|
||||
|
||||
.ping a:hover{
|
||||
text-decoration : underline;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
var node_list = {};
|
||||
var node_count = 0;
|
||||
|
||||
var ajax = {
|
||||
post: function(url, data, fn_success, timeout, fn_timeout) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
var code = ajax.encode(data);
|
||||
xhr.open("POST", url, true);
|
||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
|
||||
if (timeout && timeout > 1000) {
|
||||
xhr.timeout = timeout;
|
||||
}
|
||||
if (fn_timeout) {
|
||||
xhr.ontimeout = function() {
|
||||
fn_timeout(xhr);
|
||||
}
|
||||
}
|
||||
xhr.onreadystatechange = function() {
|
||||
if(xhr.readyState == 4 && (xhr.status == 200 || xhr.status == 304)) {
|
||||
var json = null;
|
||||
if (xhr.getResponseHeader("Content-Type") == "application/json") {
|
||||
try {
|
||||
json = eval('(' + xhr.responseText + ')');
|
||||
}
|
||||
catch(e) {
|
||||
json = null;
|
||||
}
|
||||
}
|
||||
fn_success(xhr, json);
|
||||
}
|
||||
};
|
||||
xhr.send(code);
|
||||
},
|
||||
encode: function(obj) {
|
||||
obj = obj ? obj : { };
|
||||
obj['_'] = Math.random();
|
||||
|
||||
if (typeof obj == 'object')
|
||||
{
|
||||
var code = '';
|
||||
var self = this;
|
||||
|
||||
for (var k in obj)
|
||||
code += (code ? '&' : '') +
|
||||
k + '=' + encodeURIComponent(obj[k]);
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
function copy_node(cbi_id) {
|
||||
window.location.href = '<%=api.url("copy_node")%>' + "?section=" + cbi_id;
|
||||
}
|
||||
|
||||
var section = "";
|
||||
function open_set_node_div(cbi_id) {
|
||||
section = cbi_id;
|
||||
document.getElementById("set_node_div").style.display="block";
|
||||
var node_name = document.getElementById("cbid.passwall2." + cbi_id + ".remarks").value;
|
||||
document.getElementById("set_node_name").innerHTML = node_name;
|
||||
}
|
||||
|
||||
function close_set_node_div() {
|
||||
document.getElementById("set_node_div").style.display="none";
|
||||
document.getElementById("set_node_name").innerHTML = "";
|
||||
}
|
||||
|
||||
function _cbi_row_top(id) {
|
||||
var dom = document.getElementById("cbi-passwall2-" + id);
|
||||
if (dom) {
|
||||
var trs = document.getElementById("cbi-passwall2-nodes").getElementsByClassName("cbi-section-table-row");
|
||||
if (trs && trs.length > 0) {
|
||||
for (var i = 0; i < trs.length; i++) {
|
||||
var up = dom.getElementsByClassName("cbi-button-up");
|
||||
if (up) {
|
||||
cbi_row_swap(up[0], true, 'cbi.sts.passwall2.nodes');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checked_all_node(btn) {
|
||||
var doms = document.getElementById("cbi-passwall2-nodes").getElementsByClassName("nodes_select");
|
||||
if (doms && doms.length > 0) {
|
||||
for (var i = 0 ; i < doms.length; i++) {
|
||||
doms[i].checked = true;
|
||||
}
|
||||
btn.value = "<%:DeSelect all%>";
|
||||
btn.setAttribute("onclick", "dechecked_all_node(this)");
|
||||
}
|
||||
}
|
||||
|
||||
function dechecked_all_node(btn) {
|
||||
var doms = document.getElementById("cbi-passwall2-nodes").getElementsByClassName("nodes_select");
|
||||
if (doms && doms.length > 0) {
|
||||
for (var i = 0 ; i < doms.length; i++) {
|
||||
doms[i].checked = false;
|
||||
}
|
||||
btn.value = "<%:Select all%>";
|
||||
btn.setAttribute("onclick", "checked_all_node(this)");
|
||||
}
|
||||
}
|
||||
|
||||
function delete_select_nodes() {
|
||||
var ids = [];
|
||||
var doms = document.getElementById("cbi-passwall2-nodes").getElementsByClassName("nodes_select");
|
||||
if (doms && doms.length > 0) {
|
||||
for (var i = 0 ; i < doms.length; i++) {
|
||||
if (doms[i].checked) {
|
||||
ids.push(doms[i].getAttribute("cbid"))
|
||||
}
|
||||
}
|
||||
if (ids.length > 0) {
|
||||
if (confirm('<%:Are you sure to delete select nodes?%>') == true){
|
||||
XHR.get('<%=api.url("delete_select_nodes")%>', {
|
||||
ids: ids.join()
|
||||
},
|
||||
function(x, data) {
|
||||
if (x && x.status == 200) {
|
||||
/*
|
||||
for (var i = 0 ; i < ids.length; i++) {
|
||||
var box = document.getElementById("cbi-passwall2-" + ids[i]);
|
||||
box.remove();
|
||||
}
|
||||
*/
|
||||
window.location.href = '<%=api.url("node_list")%>';
|
||||
}
|
||||
else {
|
||||
alert("<%:Error%>");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ids.length <= 0) {
|
||||
alert("<%:You no select nodes !%>");
|
||||
}
|
||||
}
|
||||
|
||||
function set_node(type, config) {
|
||||
if (confirm('<%:Are you sure set this server?%>')==true){
|
||||
window.location.href = '<%=api.url("set_node")%>?type=' + type + '&config=' + config + '§ion=' + section;
|
||||
}
|
||||
}
|
||||
|
||||
function get_address_full(id) {
|
||||
try {
|
||||
var address = document.getElementById("cbid.passwall2." + id + ".address").value;
|
||||
var port = document.getElementById("cbid.passwall2." + id + ".port").value;
|
||||
}
|
||||
catch(err){}
|
||||
//判断是否含有汉字
|
||||
var reg = new RegExp("[\\u4E00-\\u9FFF]+","g");
|
||||
if ((address != null && address != "") && (port != null && port != "") && reg.test(address) == false) {
|
||||
return { address: address, port: port };
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//获取当前使用的节点
|
||||
function get_now_use_node() {
|
||||
XHR.get('<%=api.url("get_now_use_node")%>', null,
|
||||
function(x, result) {
|
||||
var id = result["TCP"];
|
||||
if (id) {
|
||||
var dom = document.getElementById("cbi-passwall2-" + id);
|
||||
if (dom) {
|
||||
dom.classList.add("_now_use");
|
||||
dom.title = "当前TCP节点";
|
||||
//var v = "<a style='color: red'>当前TCP节点:</a>" + document.getElementById("cbid.passwall2." + id + ".remarks").value;
|
||||
//document.getElementById("cbi-passwall2-" + id + "-remarks").innerHTML = v;
|
||||
}
|
||||
}
|
||||
id = result["UDP"];
|
||||
if (id) {
|
||||
var dom = document.getElementById("cbi-passwall2-" + id);
|
||||
if (dom) {
|
||||
dom.classList.add("_now_use");
|
||||
dom.title = "当前UDP节点";
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function urltest_node(cbi_id, dom) {
|
||||
if (cbi_id != null) {
|
||||
dom.disabled = true;
|
||||
dom.value = "<%:Check...%>";
|
||||
XHR.get('<%=api.url("urltest_node")%>', {
|
||||
id: cbi_id
|
||||
},
|
||||
function(x, result) {
|
||||
if(x && x.status == 200) {
|
||||
if (result.use_time == null || result.use_time.trim() == "") {
|
||||
dom.outerHTML = "<font style='color:red'><%:Timeout%></font>";
|
||||
} else {
|
||||
var color = "red";
|
||||
var use_time = result.use_time;
|
||||
if (use_time < 1000) {
|
||||
color = "green";
|
||||
} else if (use_time < 2000) {
|
||||
color = "#fb9a05";
|
||||
} else {
|
||||
color = "red";
|
||||
}
|
||||
dom.outerHTML = "<font style='color:" + color + "'>" + result.use_time + " ms" + "</font>";
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function ping_node(cbi_id, dom) {
|
||||
var full = get_address_full(cbi_id);
|
||||
if (full != null) {
|
||||
XHR.get('<%=api.url("ping_node")%>', {
|
||||
address: full.address,
|
||||
port: full.port
|
||||
},
|
||||
function(x, result) {
|
||||
if(x && x.status == 200) {
|
||||
if (result.ping == null || result.ping.trim() == "") {
|
||||
dom.outerHTML = "<font style='color:red'><%:Timeout%></font>";
|
||||
} else {
|
||||
var ping = parseInt(result.ping);
|
||||
if (ping < 100)
|
||||
dom.outerHTML = "<font style='color:green'>" + result.ping + " ms" + "</font>";
|
||||
else if (ping < 200)
|
||||
dom.outerHTML = "<font style='color:#fb9a05'>" + result.ping + " ms" + "</font>";
|
||||
else if (ping >= 200)
|
||||
dom.outerHTML = "<font style='color:red'>" + result.ping + " ms" + "</font>";
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* 自动Ping */
|
||||
var nodes = [];
|
||||
const ping_value = document.getElementsByClassName('ping_value');
|
||||
for (var i = 0; i < ping_value.length; i++) {
|
||||
var cbi_id = ping_value[i].getAttribute("cbiid");
|
||||
var full = get_address_full(cbi_id);
|
||||
if (full != null) {
|
||||
var flag = false;
|
||||
//当有多个相同地址和端口时合在一起
|
||||
for (var j = 0; j < nodes.length; j++) {
|
||||
if (nodes[j].address == full.address && nodes[j].port == full.port) {
|
||||
nodes[j].indexs = nodes[j].indexs + "," + i;
|
||||
flag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (flag)
|
||||
continue;
|
||||
nodes.push({
|
||||
indexs: i + "",
|
||||
address: full.address,
|
||||
port: full.port
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
get_now_use_node();
|
||||
|
||||
const _xhr = (index) => {
|
||||
return new Promise((res) => {
|
||||
const dom = nodes[index];
|
||||
if (!dom) res()
|
||||
ajax.post('<%=api.url("ping_node")%>', {
|
||||
index: dom.indexs,
|
||||
address: dom.address,
|
||||
port: dom.port
|
||||
},
|
||||
function(x, result) {
|
||||
if (x && x.status == 200) {
|
||||
var strs = dom.indexs.split(",");
|
||||
for (var i = 0; i < strs.length; i++) {
|
||||
if (result.ping == null || result.ping.trim() == "") {
|
||||
ping_value[strs[i]].innerHTML = "<font style='color:red'><%:Timeout%></font>";
|
||||
} else {
|
||||
var ping = parseInt(result.ping);
|
||||
if (ping < 100)
|
||||
ping_value[strs[i]].innerHTML = "<font style='color:green'>" + result.ping + " ms" + "</font>";
|
||||
else if (ping < 200)
|
||||
ping_value[strs[i]].innerHTML = "<font style='color:#fb9a05'>" + result.ping + " ms" + "</font>";
|
||||
else if (ping >= 200)
|
||||
ping_value[strs[i]].innerHTML = "<font style='color:red'>" + result.ping + " ms" + "</font>";
|
||||
}
|
||||
}
|
||||
}
|
||||
res();
|
||||
},
|
||||
5000,
|
||||
function(x) {
|
||||
var strs = dom.indexs.split(",");
|
||||
for (var i = 0; i < strs.length; i++) {
|
||||
ping_value[strs[i]].innerHTML = "<font style='color:red'><%:Timeout%></font>";
|
||||
}
|
||||
res();
|
||||
}
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
let task = -1;
|
||||
const thread = () => {
|
||||
task = task + 1
|
||||
if (nodes[task]) {
|
||||
_xhr(task).then(thread);
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < 20; i++) {
|
||||
thread()
|
||||
}
|
||||
|
||||
/* 递归单请求方法
|
||||
var index = 0;
|
||||
function auto_ping() {
|
||||
if (index >= nodes.length) {
|
||||
return;
|
||||
}
|
||||
var indexs = nodes[index].indexs;
|
||||
var address = nodes[index].address;
|
||||
var port = nodes[index].port;
|
||||
ajax.post('<%=api.url("ping_node")%>', {
|
||||
index: indexs,
|
||||
address: address,
|
||||
port: port
|
||||
},
|
||||
function(x, result) {
|
||||
if (x && x.status == 200) {
|
||||
var strs = indexs.split(",");
|
||||
for (var i = 0; i < strs.length; i++) {
|
||||
if (result.ping == null || result.ping.trim() == "") {
|
||||
ping_value[strs[i]].innerHTML = "<font style='color:red'><%:Timeout%></font>";
|
||||
} else {
|
||||
var ping = parseInt(result.ping);
|
||||
if (ping < 100)
|
||||
ping_value[strs[i]].innerHTML = "<font style='color:green'>" + result.ping + " ms" + "</font>";
|
||||
else if (ping < 200)
|
||||
ping_value[strs[i]].innerHTML = "<font style='color:#fb9a05'>" + result.ping + " ms" + "</font>";
|
||||
else if (ping >= 200)
|
||||
ping_value[strs[i]].innerHTML = "<font style='color:red'>" + result.ping + " ms" + "</font>";
|
||||
}
|
||||
}
|
||||
}
|
||||
index++;
|
||||
return auto_ping();
|
||||
},
|
||||
function(x) {
|
||||
var strs = indexs.split(",");
|
||||
for (var i = 0; i < strs.length; i++) {
|
||||
ping_value[strs[i]].innerHTML = "<font style='color:red'><%:Timeout%></font>";
|
||||
}
|
||||
index++;
|
||||
return auto_ping();
|
||||
},
|
||||
);
|
||||
}
|
||||
auto_ping();
|
||||
*/
|
||||
|
||||
var edit_btn = document.getElementById("cbi-passwall2-nodes").getElementsByClassName("cbi-button cbi-button-edit");
|
||||
for (var i = 0; i < edit_btn.length; i++) {
|
||||
try {
|
||||
var onclick_str = edit_btn[i].getAttribute("onclick");
|
||||
var id = onclick_str.substring(onclick_str.lastIndexOf('/') + 1, onclick_str.length - 1);
|
||||
var td = edit_btn[i].parentNode;
|
||||
var new_div = "";
|
||||
//添加"勾选"框
|
||||
new_div += '<input class="cbi-input-checkbox nodes_select" type="checkbox" cbid="' + id + '" /> ';
|
||||
//添加"置顶"按钮
|
||||
new_div += '<input class="btn cbi-button" type="button" value="<%:To Top%>" onclick="_cbi_row_top(\'' + id + '\')"/> ';
|
||||
//添加"应用"按钮
|
||||
new_div += '<input class="btn cbi-button cbi-button-apply" type="button" value="<%:Use%>" id="apply_' + id + '" onclick="open_set_node_div(\'' + id + '\')"/> ';
|
||||
//添加"复制"按钮
|
||||
new_div += '<input class="btn cbi-button cbi-button-add" type="button" value="<%:Copy%>" onclick="copy_node(\'' + id + '\')"/> ';
|
||||
td.innerHTML = new_div + td.innerHTML;
|
||||
|
||||
var obj = {};
|
||||
obj.id = id;
|
||||
obj.type = document.getElementById("cbid.passwall2." + id + ".type").value;
|
||||
var address_dom = document.getElementById("cbid.passwall2." + id + ".address");
|
||||
var port_dom = document.getElementById("cbid.passwall2." + id + ".port");
|
||||
if (address_dom && port_dom) {
|
||||
obj.address = address_dom.value;
|
||||
obj.port = port_dom.value;
|
||||
}
|
||||
|
||||
node_count++;
|
||||
var add_from = document.getElementById("cbid.passwall2." + id + ".add_from").value;
|
||||
if (node_list[add_from])
|
||||
node_list[add_from].push(obj);
|
||||
else
|
||||
node_list[add_from] = [];
|
||||
|
||||
}
|
||||
catch(err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
if (true) {
|
||||
var str = "";
|
||||
for (var add_from in node_list) {
|
||||
var num = node_list[add_from].length + 1;
|
||||
if (add_from == "") {
|
||||
add_from = "<%:Self add%>";
|
||||
}
|
||||
str += add_from + " " + "<%:Node num%>: <a style='color: red'>" + num + "</a>   ";
|
||||
}
|
||||
document.getElementById("div_node_count").innerHTML = "<div style='margin-top:5px'>" + str + "</div>";
|
||||
}
|
||||
//]]>
|
||||
</script>
|
||||
|
||||
<div style="display: -webkit-flex; display: flex; -webkit-align-items: center; align-items: center; -webkit-justify-content: center; justify-content: center;">
|
||||
<div id="set_node_div">
|
||||
<div class="cbi-value"><%:You choose node is:%><a style="color: red" id="set_node_name"></a></div>
|
||||
<div class="cbi-value">
|
||||
<%- if default_node_type == "_shunt" then
|
||||
for i, v in ipairs(shunt_rule_list) do
|
||||
-%>
|
||||
<input class="btn cbi-button cbi-button-edit" type="button" onclick="set_node('<%=node%>', '<%=v[".name"]%>')" value="<%=v.remarks%>" />
|
||||
<%-
|
||||
end
|
||||
-%>
|
||||
<% else %>
|
||||
<input class="btn cbi-button cbi-button-edit" type="button" onclick="set_node('@global[0]', 'node')" value="<%:Node%>" />
|
||||
<% end %>
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" onclick="close_set_node_div()" value="<%:Close%>" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,56 @@
|
||||
<%
|
||||
local api = require "luci.passwall2.api"
|
||||
|
||||
local geoip_update = api.uci_get_type("global_rules", "geoip_update", "1") == "1" and "checked='checked'" or ""
|
||||
local geosite_update = api.uci_get_type("global_rules", "geosite_update", "1") == "1" and "checked='checked'" or ""
|
||||
-%>
|
||||
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
function update_rules(btn) {
|
||||
btn.disabled = true;
|
||||
btn.value = '<%:Updating...%>';
|
||||
var div = document.getElementById('_rule_div');
|
||||
var domList = div.getElementsByTagName('input');
|
||||
var checkBoxList = [];
|
||||
var len = domList.length;
|
||||
while(len--) {
|
||||
var dom = domList[len];
|
||||
if(dom.type == 'checkbox' && dom.checked) {
|
||||
checkBoxList.push(dom.name);
|
||||
}
|
||||
}
|
||||
XHR.get('<%=api.url("update_rules")%>', {
|
||||
update: checkBoxList.join(",")
|
||||
},
|
||||
function(x, data) {
|
||||
if(x && x.status == 200) {
|
||||
window.location.href = '<%=api.url("log")%>';
|
||||
} else {
|
||||
alert("<%:Error%>");
|
||||
btn.disabled = false;
|
||||
btn.value = '<%:Manually update%>';
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
//]]>
|
||||
</script>
|
||||
<div class="cbi-value" id="_rule_div">
|
||||
<label class="cbi-value-title">
|
||||
<%:Manually update%>
|
||||
</label>
|
||||
<div class="cbi-value-field">
|
||||
<div>
|
||||
<label>
|
||||
<input class="cbi-input-checkbox" type="checkbox" name="geoip" value="1" <%=geoip_update%> />
|
||||
geoip
|
||||
</label>
|
||||
<label>
|
||||
<input class="cbi-input-checkbox" type="checkbox" name="geosite" value="1" <%=geosite_update%> />
|
||||
geosite
|
||||
</label>
|
||||
<input class="btn cbi-button cbi-button-apply" type="button" id="update_rules_btn" onclick="update_rules(this)" value="<%:Manually update%>" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
34
luci-app-passwall2/luasrc/view/passwall2/server/log.htm
Normal file
34
luci-app-passwall2/luasrc/view/passwall2/server/log.htm
Normal file
@ -0,0 +1,34 @@
|
||||
<%
|
||||
local api = require "luci.passwall2.api"
|
||||
-%>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
function clear_log(btn) {
|
||||
XHR.get('<%=api.url("server_clear_log")%>', null,
|
||||
function(x, data) {
|
||||
if(x && x.status == 200) {
|
||||
var log_textarea = document.getElementById('log_textarea');
|
||||
log_textarea.innerHTML = "";
|
||||
log_textarea.scrollTop = log_textarea.scrollHeight;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
XHR.poll(3, '<%=api.url("server_get_log")%>', null,
|
||||
function(x, data) {
|
||||
if(x && x.status == 200) {
|
||||
var log_textarea = document.getElementById('log_textarea');
|
||||
log_textarea.innerHTML = x.responseText;
|
||||
log_textarea.scrollTop = log_textarea.scrollHeight;
|
||||
}
|
||||
}
|
||||
);
|
||||
//]]>
|
||||
</script>
|
||||
<fieldset class="cbi-section" id="_log_fieldset">
|
||||
<legend>
|
||||
<%:Logs%>
|
||||
</legend>
|
||||
<input class="btn cbi-button cbi-button-remove" type="button" onclick="clear_log()" value="<%:Clear logs%>" />
|
||||
<textarea id="log_textarea" class="cbi-input-textarea" style="width: 100%;margin-top: 10px;" data-update="change" rows="20" wrap="off" readonly="readonly"></textarea>
|
||||
</fieldset>
|
@ -0,0 +1,38 @@
|
||||
<%
|
||||
local api = require "luci.passwall2.api"
|
||||
-%>
|
||||
<script type="text/javascript">
|
||||
//<![CDATA[
|
||||
var _users_status = document.getElementsByClassName('_users_status');
|
||||
for(var i = 0; i < _users_status.length; i++) {
|
||||
var id = _users_status[i].parentElement.parentElement.parentElement.id;
|
||||
id = id.substr(id.lastIndexOf("-") + 1);
|
||||
XHR.get('<%=api.url("server_user_status")%>', {
|
||||
index: i,
|
||||
id: id
|
||||
},
|
||||
function(x, result) {
|
||||
_users_status[result.index].setAttribute("style","font-weight:bold;");
|
||||
_users_status[result.index].setAttribute("color",result.status ? "green":"red");
|
||||
_users_status[result.index].innerHTML = (result.status ? '✓' : 'X');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
var edit_btn = document.getElementById("cbi-passwall2_server-user").getElementsByClassName("cbi-button cbi-button-edit");
|
||||
for (var i = 0; i < edit_btn.length; i++) {
|
||||
try {
|
||||
var onclick_str = edit_btn[i].getAttribute("onclick");
|
||||
var id = onclick_str.substring(onclick_str.lastIndexOf('/') + 1, onclick_str.length - 1);
|
||||
var td = edit_btn[i].parentNode;
|
||||
var new_div = "";
|
||||
//添加"日志"按钮
|
||||
new_div += '<input class="btn cbi-button cbi-button-add" type="button" value="<%:Log%>" onclick="window.open(\'' + '<%=api.url("server_user_log")%>' + '?id=' + id + '\', \'_blank\')"/> ';
|
||||
td.innerHTML = new_div + td.innerHTML;
|
||||
}
|
||||
catch(err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
//]]>
|
||||
</script>
|
1316
luci-app-passwall2/po/zh-cn/passwall2.po
Normal file
1316
luci-app-passwall2/po/zh-cn/passwall2.po
Normal file
File diff suppressed because it is too large
Load Diff
1
luci-app-passwall2/po/zh_Hans
Symbolic link
1
luci-app-passwall2/po/zh_Hans
Symbolic link
@ -0,0 +1 @@
|
||||
zh-cn
|
4
luci-app-passwall2/root/etc/config/passwall2_server
Normal file
4
luci-app-passwall2/root/etc/config/passwall2_server
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
config global 'global'
|
||||
option enable '0'
|
||||
|
23
luci-app-passwall2/root/etc/hotplug.d/iface/98-passwall2
Normal file
23
luci-app-passwall2/root/etc/hotplug.d/iface/98-passwall2
Normal file
@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
|
||||
[[ "$ACTION" == "ifup" && $(uci get "passwall2.@global[0].enabled") == "1" ]] && [ -f /var/lock/passwall2_ready.lock ] && {
|
||||
default_device=$(ip route | grep default | awk -F 'dev ' '{print $2}' | awk '{print $1}')
|
||||
[ "$default_device" == "$DEVICE" ] && {
|
||||
LOCK_FILE_DIR=/var/lock
|
||||
[ ! -d ${LOCK_FILE_DIR} ] && mkdir -p ${LOCK_FILE_DIR}
|
||||
LOCK_FILE="${LOCK_FILE_DIR}/passwall2_ifup.lock"
|
||||
if [ -s ${LOCK_FILE} ]; then
|
||||
SPID=$(cat ${LOCK_FILE})
|
||||
if [ -e /proc/${SPID}/status ]; then
|
||||
exit 1
|
||||
fi
|
||||
cat /dev/null > ${LOCK_FILE}
|
||||
fi
|
||||
echo $$ > ${LOCK_FILE}
|
||||
|
||||
/etc/init.d/passwall2 restart
|
||||
echo "passwall2: restart when $INTERFACE ifup" > /dev/kmsg
|
||||
|
||||
rm -rf ${LOCK_FILE}
|
||||
}
|
||||
}
|
66
luci-app-passwall2/root/etc/init.d/passwall2
Executable file
66
luci-app-passwall2/root/etc/init.d/passwall2
Executable file
@ -0,0 +1,66 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
START=99
|
||||
STOP=15
|
||||
|
||||
CONFIG=passwall2
|
||||
APP_FILE=/usr/share/${CONFIG}/app.sh
|
||||
LOCK_FILE_DIR=/var/lock
|
||||
LOCK_FILE=${LOCK_FILE_DIR}/${CONFIG}.lock
|
||||
|
||||
set_lock() {
|
||||
[ ! -d "$LOCK_FILE_DIR" ] && mkdir -p $LOCK_FILE_DIR
|
||||
exec 999>"$LOCK_FILE"
|
||||
flock -xn 999
|
||||
}
|
||||
|
||||
unset_lock() {
|
||||
flock -u 999
|
||||
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
|
||||
}
|
||||
|
||||
boot() {
|
||||
local delay=$(uci -q get ${CONFIG}.@global_delay[0].start_delay || echo 1)
|
||||
if [ "$delay" -gt 0 ]; then
|
||||
$APP_FILE echolog "执行启动延时 $delay 秒后再启动!"
|
||||
sleep $delay
|
||||
fi
|
||||
restart
|
||||
touch ${LOCK_FILE_DIR}/${CONFIG}_ready.lock
|
||||
}
|
||||
|
||||
start() {
|
||||
set_lock
|
||||
[ $? == 1 ] && $APP_FILE echolog "脚本已经在运行,不重复运行,退出." && exit 0
|
||||
$APP_FILE start
|
||||
unset_lock
|
||||
}
|
||||
|
||||
stop() {
|
||||
unlock
|
||||
set_lock
|
||||
[ $? == 1 ] && $APP_FILE echolog "停止脚本等待超时,不重复运行,退出." && exit 0
|
||||
$APP_FILE stop
|
||||
unset_lock
|
||||
}
|
||||
|
||||
restart() {
|
||||
set_lock
|
||||
[ $? == 1 ] && $APP_FILE echolog "脚本已经在运行,不重复运行,退出." && exit 0
|
||||
$APP_FILE stop
|
||||
$APP_FILE start
|
||||
unset_lock
|
||||
}
|
16
luci-app-passwall2/root/etc/init.d/passwall2_server
Executable file
16
luci-app-passwall2/root/etc/init.d/passwall2_server
Executable file
@ -0,0 +1,16 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
START=99
|
||||
|
||||
start() {
|
||||
lua /usr/lib/lua/luci/passwall2/server_app.lua start
|
||||
}
|
||||
|
||||
stop() {
|
||||
lua /usr/lib/lua/luci/passwall2/server_app.lua stop
|
||||
}
|
||||
|
||||
restart() {
|
||||
stop
|
||||
start
|
||||
}
|
49
luci-app-passwall2/root/etc/uci-defaults/luci-passwall2
Executable file
49
luci-app-passwall2/root/etc/uci-defaults/luci-passwall2
Executable file
@ -0,0 +1,49 @@
|
||||
#!/bin/sh
|
||||
|
||||
uci -q batch <<-EOF >/dev/null
|
||||
set dhcp.@dnsmasq[0].localuse=1
|
||||
commit dhcp
|
||||
delete ucitrack.@passwall2[-1]
|
||||
add ucitrack passwall2
|
||||
set ucitrack.@passwall2[-1].init=passwall2
|
||||
commit ucitrack
|
||||
delete firewall.passwall2
|
||||
set firewall.passwall2=include
|
||||
set firewall.passwall2.type=script
|
||||
set firewall.passwall2.path=/var/etc/passwall2.include
|
||||
set firewall.passwall2.reload=1
|
||||
commit firewall
|
||||
delete ucitrack.@passwall2_server[-1]
|
||||
add ucitrack passwall2_server
|
||||
set ucitrack.@passwall2_server[-1].init=passwall2_server
|
||||
commit ucitrack
|
||||
delete firewall.passwall2_server
|
||||
set firewall.passwall2_server=include
|
||||
set firewall.passwall2_server.type=script
|
||||
set firewall.passwall2_server.path=/var/etc/passwall2_server.include
|
||||
set firewall.passwall2_server.reload=1
|
||||
commit firewall
|
||||
set uhttpd.main.max_requests=50
|
||||
commit uhttpd
|
||||
EOF
|
||||
|
||||
touch /etc/config/passwall2_show >/dev/null 2>&1
|
||||
[ ! -s "/etc/config/passwall2" ] && cp -f /usr/share/passwall2/0_default_config /etc/config/passwall2
|
||||
|
||||
use_nft=$(uci -q get passwall2.@global_forwarding[0].use_nft || echo "0")
|
||||
[ "${use_nft}" = "0" ] && {
|
||||
if [ -z "$(command -v iptables-legacy || command -v iptables)" ] || [ -z "$(command -v ipset)" ] || [ -z "$(dnsmasq --version | grep 'Compile time options:.* ipset')" ]; then
|
||||
[ "$(opkg list-installed | grep "firewall4")" ] && [ "$(opkg list-installed | grep "nftables")" ] && {
|
||||
[ "$(opkg list-installed | grep "kmod\-nft\-socket")" ] && [ "$(opkg list-installed | grep "kmod\-nft\-tproxy")" ] && [ "$(opkg list-installed | grep "kmod\-nft\-nat")" ] && {
|
||||
uci -q set passwall2.@global_forwarding[0].use_nft=1
|
||||
uci -q commit passwall2
|
||||
sed -i "s#use_nft '0'#use_nft '1'#g" /usr/share/passwall2/0_default_config
|
||||
}
|
||||
}
|
||||
fi
|
||||
}
|
||||
|
||||
rm -f /tmp/luci-indexcache
|
||||
rm -rf /tmp/luci-modulecache/
|
||||
killall -HUP rpcd 2>/dev/null
|
||||
exit 0
|
244
luci-app-passwall2/root/usr/share/passwall2/0_default_config
Normal file
244
luci-app-passwall2/root/usr/share/passwall2/0_default_config
Normal file
@ -0,0 +1,244 @@
|
||||
|
||||
config global
|
||||
option enabled '0'
|
||||
option node_socks_port '1070'
|
||||
option localhost_proxy '1'
|
||||
option socks_enabled '0'
|
||||
option acl_enable '0'
|
||||
option node 'myshunt'
|
||||
option direct_dns_protocol 'auto'
|
||||
option direct_dns_query_strategy 'UseIP'
|
||||
option remote_dns_protocol 'tcp'
|
||||
option remote_dns '1.1.1.1'
|
||||
option remote_dns_query_strategy 'UseIPv4'
|
||||
option dns_hosts 'cloudflare-dns.com 1.1.1.1
|
||||
dns.google.com 8.8.8.8'
|
||||
option close_log '0'
|
||||
option loglevel 'error'
|
||||
|
||||
config global_haproxy
|
||||
option balancing_enable '0'
|
||||
|
||||
config global_delay
|
||||
option auto_on '0'
|
||||
option start_daemon '1'
|
||||
option start_delay '60'
|
||||
|
||||
config global_forwarding
|
||||
option tcp_no_redir_ports 'disable'
|
||||
option udp_no_redir_ports 'disable'
|
||||
option tcp_redir_ports '22,25,53,143,465,587,853,993,995,80,443'
|
||||
option udp_redir_ports '1:65535'
|
||||
option accept_icmp '0'
|
||||
option use_nft '0'
|
||||
option tcp_proxy_way 'redirect'
|
||||
option ipv6_tproxy '0'
|
||||
option sniffing '1'
|
||||
option route_only '0'
|
||||
|
||||
config global_other
|
||||
option nodes_ping 'auto_ping tcping'
|
||||
|
||||
config global_rules
|
||||
option auto_update '0'
|
||||
option geosite_update '1'
|
||||
option geoip_update '1'
|
||||
option v2ray_location_asset '/usr/share/v2ray/'
|
||||
|
||||
config global_app
|
||||
option v2ray_file '/usr/bin/v2ray'
|
||||
option xray_file '/usr/bin/xray'
|
||||
option brook_file '/usr/bin/brook'
|
||||
option hysteria_file '/usr/bin/hysteria'
|
||||
|
||||
config global_subscribe
|
||||
option filter_keyword_mode '1'
|
||||
list filter_discard_list '过期时间'
|
||||
list filter_discard_list '剩余流量'
|
||||
list filter_discard_list 'QQ群'
|
||||
list filter_discard_list '官网'
|
||||
|
||||
config auto_switch
|
||||
option enable '0'
|
||||
option testing_time '1'
|
||||
option connect_timeout '3'
|
||||
option retry_num '3'
|
||||
option shunt_logic '1'
|
||||
|
||||
config nodes 'myshunt'
|
||||
option remarks '分流总节点'
|
||||
option type 'Xray'
|
||||
option protocol '_shunt'
|
||||
option DirectGame '_direct'
|
||||
option ProxyGame '_default'
|
||||
option Direct '_direct'
|
||||
option AD 'nil'
|
||||
option BT '_direct'
|
||||
option Netflix 'nil'
|
||||
option OpenAI 'nil'
|
||||
option TVB 'nil'
|
||||
option Proxy '_default'
|
||||
option China '_direct'
|
||||
option QUIC '_blackhole'
|
||||
option UDP 'nil'
|
||||
option default_node 'nil'
|
||||
option domainStrategy 'IPOnDemand'
|
||||
option domainMatcher 'hybrid'
|
||||
|
||||
config shunt_rules 'DirectGame'
|
||||
option remarks 'DirectGame'
|
||||
option network 'tcp,udp'
|
||||
option domain_list 'api.steampowered.com
|
||||
regexp:\.cm.steampowered.com$
|
||||
regexp:\.steamserver.net$
|
||||
geosite:category-games@cn
|
||||
'
|
||||
|
||||
option ip_list '103.10.124.0/24
|
||||
103.10.125.0/24
|
||||
103.28.54.0/24
|
||||
146.66.152.0/24
|
||||
146.66.155.0/24
|
||||
153.254.86.0/24
|
||||
155.133.224.0/23
|
||||
155.133.226.0/24
|
||||
155.133.227.0/24
|
||||
155.133.230.0/24
|
||||
155.133.232.0/24
|
||||
155.133.233.0/24
|
||||
155.133.234.0/24
|
||||
155.133.236.0/23
|
||||
155.133.238.0/24
|
||||
155.133.239.0/24
|
||||
155.133.240.0/23
|
||||
155.133.245.0/24
|
||||
155.133.246.0/24
|
||||
155.133.248.0/24
|
||||
155.133.249.0/24
|
||||
155.133.250.0/24
|
||||
155.133.251.0/24
|
||||
155.133.252.0/24
|
||||
155.133.253.0/24
|
||||
155.133.254.0/24
|
||||
155.133.255.0/24
|
||||
162.254.192.0/24
|
||||
162.254.193.0/24
|
||||
162.254.194.0/23
|
||||
162.254.195.0/24
|
||||
162.254.196.0/24
|
||||
162.254.197.0/24
|
||||
162.254.198.0/24
|
||||
162.254.199.0/24
|
||||
185.25.182.0/24
|
||||
185.25.183.0/24
|
||||
190.217.33.0/24
|
||||
192.69.96.0/22
|
||||
205.185.194.0/24
|
||||
205.196.6.0/24
|
||||
208.64.200.0/24
|
||||
208.64.201.0/24
|
||||
208.64.202.0/24
|
||||
208.64.203.0/24
|
||||
208.78.164.0/22'
|
||||
|
||||
config shunt_rules 'ProxyGame'
|
||||
option remarks 'ProxyGame'
|
||||
option domain_list 'geosite:category-games@!cn
|
||||
domain:store.steampowered.com
|
||||
'
|
||||
|
||||
config shunt_rules 'Direct'
|
||||
option network 'tcp,udp'
|
||||
option remarks 'Direct'
|
||||
option ip_list 'geoip:private
|
||||
114.114.114.114
|
||||
114.114.115.115
|
||||
223.5.5.5
|
||||
223.6.6.6
|
||||
119.29.29.29
|
||||
180.76.76.76
|
||||
'
|
||||
option domain_list 'apple.com
|
||||
microsoft.com
|
||||
dyndns.com
|
||||
steamcontent.com
|
||||
dl.steam.clngaa.com
|
||||
dl.steam.ksyna.com
|
||||
st.dl.bscstorage.net
|
||||
st.dl.eccdnx.com
|
||||
st.dl.pinyuncloud.com
|
||||
cdn.mileweb.cs.steampowered.com.8686c.com
|
||||
cdn-ws.content.steamchina.com
|
||||
cdn-qc.content.steamchina.com
|
||||
cdn-ali.content.steamchina.com
|
||||
epicgames-download1-1251447533.file.myqcloud.com'
|
||||
|
||||
config shunt_rules 'AD'
|
||||
option remarks 'AD'
|
||||
option domain_list 'geosite:category-ads-all'
|
||||
option network 'tcp,udp'
|
||||
|
||||
config shunt_rules 'BT'
|
||||
option remarks 'BT'
|
||||
option protocol 'bittorrent'
|
||||
option network 'tcp,udp'
|
||||
|
||||
config shunt_rules 'Netflix'
|
||||
option remarks 'Netflix'
|
||||
option network 'tcp,udp'
|
||||
option domain_list 'geosite:netflix'
|
||||
|
||||
config shunt_rules 'OpenAI'
|
||||
option remarks 'OpenAI'
|
||||
option domain_list 'geosite:openai'
|
||||
|
||||
config shunt_rules 'TVB'
|
||||
option remarks 'TVB'
|
||||
option network 'tcp,udp'
|
||||
option domain_list 'geosite:tvb
|
||||
geosite:mytvsuper
|
||||
'
|
||||
|
||||
config shunt_rules 'Proxy'
|
||||
option network 'tcp,udp'
|
||||
option remarks 'Proxy'
|
||||
option domain_list 'geosite:geolocation-!cn'
|
||||
option ip_list '149.154.160.0/20
|
||||
91.108.4.0/22
|
||||
91.108.56.0/24
|
||||
109.239.140.0/24
|
||||
67.198.55.0/24
|
||||
8.8.4.4
|
||||
8.8.8.8
|
||||
208.67.222.222
|
||||
208.67.220.220
|
||||
1.1.1.1
|
||||
1.1.1.2
|
||||
1.0.0.1
|
||||
9.9.9.9
|
||||
149.112.112.112
|
||||
2001:67c:4e8::/48
|
||||
2001:b28:f23c::/48
|
||||
2001:b28:f23d::/48
|
||||
2001:b28:f23f::/48
|
||||
2001:b28:f242::/48
|
||||
2001:4860:4860::8888
|
||||
2001:4860:4860::8844
|
||||
2606:4700:4700::1111
|
||||
2606:4700:4700::1001
|
||||
'
|
||||
|
||||
config shunt_rules 'China'
|
||||
option remarks 'China'
|
||||
option network 'tcp,udp'
|
||||
option ip_list 'geoip:cn'
|
||||
option domain_list 'geosite:cn'
|
||||
|
||||
config shunt_rules 'QUIC'
|
||||
option remarks 'QUIC'
|
||||
option port '80,443'
|
||||
option network 'udp'
|
||||
|
||||
config shunt_rules 'UDP'
|
||||
option remarks 'UDP'
|
||||
option network 'udp'
|
1087
luci-app-passwall2/root/usr/share/passwall2/app.sh
Executable file
1087
luci-app-passwall2/root/usr/share/passwall2/app.sh
Executable file
File diff suppressed because it is too large
Load Diff
24
luci-app-passwall2/root/usr/share/passwall2/domains_excluded
Normal file
24
luci-app-passwall2/root/usr/share/passwall2/domains_excluded
Normal file
@ -0,0 +1,24 @@
|
||||
courier.push.apple.com
|
||||
rbsxbxp-mim.vivox.com
|
||||
rbsxbxp.www.vivox.com
|
||||
rbsxbxp-ws.vivox.com
|
||||
rbspsxp.www.vivox.com
|
||||
rbspsxp-mim.vivox.com
|
||||
rbspsxp-ws.vivox.com
|
||||
rbswxp.www.vivox.com
|
||||
rbswxp-mim.vivox.com
|
||||
disp-rbspsp-5-1.vivox.com
|
||||
disp-rbsxbp-5-1.vivox.com
|
||||
proxy.rbsxbp.vivox.com
|
||||
proxy.rbspsp.vivox.com
|
||||
proxy.rbswp.vivox.com
|
||||
rbswp.vivox.com
|
||||
rbsxbp.vivox.com
|
||||
rbspsp.vivox.com
|
||||
rbspsp.www.vivox.com
|
||||
rbswp.www.vivox.com
|
||||
rbsxbp.www.vivox.com
|
||||
rbsxbxp.vivox.com
|
||||
rbspsxp.vivox.com
|
||||
rbswxp.vivox.com
|
||||
Mijia Cloud
|
219
luci-app-passwall2/root/usr/share/passwall2/haproxy.lua
Normal file
219
luci-app-passwall2/root/usr/share/passwall2/haproxy.lua
Normal file
@ -0,0 +1,219 @@
|
||||
#!/usr/bin/lua
|
||||
|
||||
local api = require ("luci.passwall2.api")
|
||||
local appname = api.appname
|
||||
local fs = api.fs
|
||||
local jsonc = api.jsonc
|
||||
local uci = api.uci
|
||||
local sys = api.sys
|
||||
|
||||
local log = function(...)
|
||||
api.log(...)
|
||||
end
|
||||
|
||||
function get_ip_port_from(str)
|
||||
local result_port = sys.exec("echo -n " .. str .. " | sed -n 's/^.*[:#]\\([0-9]*\\)$/\\1/p'")
|
||||
local result_ip = sys.exec(string.format("__host=%s;__varport=%s;", str, result_port) .. "echo -n ${__host%%${__varport:+[:#]${__varport}*}}")
|
||||
return result_ip, result_port
|
||||
end
|
||||
|
||||
local new_port
|
||||
local function get_new_port()
|
||||
if new_port then
|
||||
new_port = tonumber(sys.exec(string.format("echo -n $(/usr/share/%s/app.sh get_new_port %s tcp)", appname, new_port + 1)))
|
||||
else
|
||||
new_port = tonumber(sys.exec(string.format("echo -n $(/usr/share/%s/app.sh get_new_port auto tcp)", appname)))
|
||||
end
|
||||
return new_port
|
||||
end
|
||||
|
||||
local var = api.get_args(arg)
|
||||
local haproxy_path = var["-path"]
|
||||
local haproxy_conf = var["-conf"]
|
||||
local haproxy_dns = var["-dns"] or "119.29.29.29:53,223.5.5.5:53"
|
||||
|
||||
local cpu_thread = sys.exec('echo -n $(cat /proc/cpuinfo | grep "processor" | wc -l)') or "1"
|
||||
local health_check_type = uci:get(appname, "@global_haproxy[0]", "health_check_type") or "tcp"
|
||||
local health_check_inter = uci:get(appname, "@global_haproxy[0]", "health_check_inter") or "10"
|
||||
|
||||
log("HAPROXY 负载均衡...")
|
||||
fs.mkdir(haproxy_path)
|
||||
local haproxy_file = haproxy_path .. "/" .. haproxy_conf
|
||||
|
||||
local f_out = io.open(haproxy_file, "a")
|
||||
|
||||
local haproxy_config = [[
|
||||
global
|
||||
daemon
|
||||
log 127.0.0.1 local2
|
||||
maxconn 60000
|
||||
stats socket {{path}}/haproxy.sock
|
||||
nbthread {{nbthread}}
|
||||
external-check
|
||||
insecure-fork-wanted
|
||||
|
||||
defaults
|
||||
mode tcp
|
||||
log global
|
||||
option tcplog
|
||||
option dontlognull
|
||||
option http-server-close
|
||||
#option forwardfor except 127.0.0.0/8
|
||||
option redispatch
|
||||
retries 2
|
||||
timeout http-request 10s
|
||||
timeout queue 1m
|
||||
timeout connect 10s
|
||||
timeout client 1m
|
||||
timeout server 1m
|
||||
timeout http-keep-alive 10s
|
||||
timeout check 10s
|
||||
maxconn 3000
|
||||
|
||||
resolvers mydns
|
||||
resolve_retries 1
|
||||
timeout resolve 5s
|
||||
hold valid 600s
|
||||
{{dns}}
|
||||
]]
|
||||
|
||||
haproxy_config = haproxy_config:gsub("{{path}}", haproxy_path)
|
||||
haproxy_config = haproxy_config:gsub("{{nbthread}}", cpu_thread)
|
||||
|
||||
local mydns = ""
|
||||
local index = 0
|
||||
string.gsub(haproxy_dns, '[^' .. "," .. ']+', function(w)
|
||||
index = index + 1
|
||||
local s = w:gsub("#", ":")
|
||||
if not s:find(":") then
|
||||
s = s .. ":53"
|
||||
end
|
||||
mydns = mydns .. (index > 1 and "\n" or "") .. " " .. string.format("nameserver dns%s %s", index, s)
|
||||
end)
|
||||
haproxy_config = haproxy_config:gsub("{{dns}}", mydns)
|
||||
|
||||
f_out:write(haproxy_config)
|
||||
|
||||
local listens = {}
|
||||
|
||||
uci:foreach(appname, "haproxy_config", function(t)
|
||||
if t.enabled == "1" then
|
||||
local server_remark
|
||||
local server_address
|
||||
local server_port
|
||||
local lbss = t.lbss
|
||||
local listen_port = tonumber(t.haproxy_port) or 0
|
||||
local server_node = uci:get_all(appname, lbss)
|
||||
if server_node and server_node.address and server_node.port then
|
||||
server_remark = server_node.address .. ":" .. server_node.port
|
||||
server_address = server_node.address
|
||||
server_port = server_node.port
|
||||
t.origin_address = server_address
|
||||
t.origin_port = server_port
|
||||
if health_check_type == "passwall_logic" then
|
||||
if server_node.type ~= "Socks" then
|
||||
local relay_port = server_node.port
|
||||
new_port = get_new_port()
|
||||
local config_file = string.format("haproxy_%s_%s.json", t[".name"], new_port)
|
||||
sys.call(string.format('/usr/share/%s/app.sh run_socks "%s"> /dev/null',
|
||||
appname,
|
||||
string.format("flag=%s node=%s bind=%s socks_port=%s config_file=%s",
|
||||
new_port, --flag
|
||||
server_node[".name"], --node
|
||||
"127.0.0.1", --bind
|
||||
new_port, --socks port
|
||||
config_file --config file
|
||||
)
|
||||
)
|
||||
)
|
||||
server_address = "127.0.0.1"
|
||||
server_port = new_port
|
||||
end
|
||||
end
|
||||
else
|
||||
server_address, server_port = get_ip_port_from(lbss)
|
||||
server_remark = server_address .. ":" .. server_port
|
||||
t.origin_address = server_address
|
||||
t.origin_port = server_port
|
||||
end
|
||||
if server_address and server_port and listen_port > 0 then
|
||||
if not listens[listen_port] then
|
||||
listens[listen_port] = {}
|
||||
end
|
||||
t.server_remark = server_remark
|
||||
t.server_address = server_address
|
||||
t.server_port = server_port
|
||||
table.insert(listens[listen_port], t)
|
||||
else
|
||||
log(" - 丢弃1个明显无效的节点")
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
local sortTable = {}
|
||||
for i in pairs(listens) do
|
||||
if i ~= nil then
|
||||
table.insert(sortTable, i)
|
||||
end
|
||||
end
|
||||
table.sort(sortTable, function(a,b) return (a < b) end)
|
||||
|
||||
for i, port in pairs(sortTable) do
|
||||
log(" + 入口 0.0.0.0:%s..." % port)
|
||||
|
||||
f_out:write("\n" .. string.format([[
|
||||
listen %s
|
||||
bind 0.0.0.0:%s
|
||||
mode tcp
|
||||
balance roundrobin
|
||||
]], port, port))
|
||||
|
||||
if health_check_type == "passwall_logic" then
|
||||
f_out:write(string.format([[
|
||||
option external-check
|
||||
external-check command "/usr/share/passwall2/haproxy_check.sh"
|
||||
]], port, port))
|
||||
end
|
||||
|
||||
for i, o in ipairs(listens[port]) do
|
||||
local remark = o.server_remark
|
||||
local server = o.server_address .. ":" .. o.server_port
|
||||
local server_conf = "server {{remark}} {{server}} weight {{weight}} {{resolvers}} check inter {{inter}} rise 1 fall 3 {{backup}}"
|
||||
server_conf = server_conf:gsub("{{remark}}", remark)
|
||||
server_conf = server_conf:gsub("{{server}}", server)
|
||||
server_conf = server_conf:gsub("{{weight}}", o.lbweight)
|
||||
local resolvers = "resolvers mydns"
|
||||
if api.is_ip(o.server_address) then
|
||||
resolvers = ""
|
||||
end
|
||||
server_conf = server_conf:gsub("{{resolvers}}", resolvers)
|
||||
server_conf = server_conf:gsub("{{inter}}", tonumber(health_check_inter) .. "s")
|
||||
server_conf = server_conf:gsub("{{backup}}", o.backup == "1" and "backup" or "")
|
||||
|
||||
f_out:write(" " .. server_conf .. "\n")
|
||||
|
||||
if o.export ~= "0" then
|
||||
sys.call(string.format("/usr/share/passwall2/app.sh add_ip2route %s %s", o.origin_address, o.export))
|
||||
end
|
||||
|
||||
log(string.format(" | - 出口节点:%s:%s,权重:%s", o.origin_address, o.origin_port, o.lbweight))
|
||||
end
|
||||
end
|
||||
|
||||
--控制台配置
|
||||
local console_port = uci:get(appname, "@global_haproxy[0]", "console_port")
|
||||
local console_user = uci:get(appname, "@global_haproxy[0]", "console_user")
|
||||
local console_password = uci:get(appname, "@global_haproxy[0]", "console_password")
|
||||
local str = [[
|
||||
listen console
|
||||
bind 0.0.0.0:%s
|
||||
mode http
|
||||
stats refresh 30s
|
||||
stats uri /
|
||||
stats admin if TRUE
|
||||
%s
|
||||
]]
|
||||
f_out:write("\n" .. string.format(str, console_port, (console_user and console_user ~= "" and console_password and console_password ~= "") and "stats auth " .. console_user .. ":" .. console_password or ""))
|
||||
log(string.format(" * 控制台端口:%s", console_port))
|
||||
|
||||
f_out:close()
|
18
luci-app-passwall2/root/usr/share/passwall2/haproxy_check.sh
Executable file
18
luci-app-passwall2/root/usr/share/passwall2/haproxy_check.sh
Executable file
@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
|
||||
listen_address=$1
|
||||
listen_port=$2
|
||||
server_address=$3
|
||||
server_port=$4
|
||||
status=$(/usr/bin/curl -I -o /dev/null -skL -x socks5h://${server_address}:${server_port} --connect-timeout 3 --retry 3 -w %{http_code} "https://www.google.com/generate_204")
|
||||
case "$status" in
|
||||
204|\
|
||||
200)
|
||||
status=200
|
||||
;;
|
||||
esac
|
||||
return_code=1
|
||||
if [ "$status" = "200" ]; then
|
||||
return_code=0
|
||||
fi
|
||||
exit ${return_code}
|
146
luci-app-passwall2/root/usr/share/passwall2/helper_dnsmasq.sh
Executable file
146
luci-app-passwall2/root/usr/share/passwall2/helper_dnsmasq.sh
Executable file
@ -0,0 +1,146 @@
|
||||
#!/bin/sh
|
||||
|
||||
stretch() {
|
||||
#zhenduiluanshezhiDNSderen
|
||||
local dnsmasq_server=$(uci -q get dhcp.@dnsmasq[0].server)
|
||||
local dnsmasq_noresolv=$(uci -q get dhcp.@dnsmasq[0].noresolv)
|
||||
local _flag
|
||||
for server in $dnsmasq_server; do
|
||||
[ -z "$(echo $server | grep '\/')" ] && _flag=1
|
||||
done
|
||||
[ -z "$_flag" ] && [ "$dnsmasq_noresolv" = "1" ] && {
|
||||
uci -q delete dhcp.@dnsmasq[0].noresolv
|
||||
uci -q set dhcp.@dnsmasq[0].resolvfile="$RESOLVFILE"
|
||||
uci commit dhcp
|
||||
}
|
||||
}
|
||||
|
||||
backup_servers() {
|
||||
DNSMASQ_DNS=$(uci show dhcp | grep "@dnsmasq" | grep ".server=" | awk -F '=' '{print $2}' | sed "s/'//g" | tr ' ' ',')
|
||||
if [ -n "${DNSMASQ_DNS}" ]; then
|
||||
uci -q set $CONFIG.@global[0].dnsmasq_servers="${DNSMASQ_DNS}"
|
||||
uci commit $CONFIG
|
||||
fi
|
||||
}
|
||||
|
||||
restore_servers() {
|
||||
OLD_SERVER=$(uci -q get $CONFIG.@global[0].dnsmasq_servers | tr "," " ")
|
||||
for server in $OLD_SERVER; do
|
||||
uci -q del_list dhcp.@dnsmasq[0].server=$server
|
||||
uci -q add_list dhcp.@dnsmasq[0].server=$server
|
||||
done
|
||||
uci commit dhcp
|
||||
uci -q delete $CONFIG.@global[0].dnsmasq_servers
|
||||
uci commit $CONFIG
|
||||
}
|
||||
|
||||
logic_restart() {
|
||||
local no_log
|
||||
eval_set_val $@
|
||||
_LOG_FILE=$LOG_FILE
|
||||
[ -n "$no_log" ] && LOG_FILE="/dev/null"
|
||||
if [ -f "$TMP_PATH/default_DNS" ]; then
|
||||
backup_servers
|
||||
#sed -i "/list server/d" /etc/config/dhcp >/dev/null 2>&1
|
||||
for server in $(uci -q get dhcp.@dnsmasq[0].server); do
|
||||
[ -n "$(echo $server | grep '\/')" ] || uci -q del_list dhcp.@dnsmasq[0].server="$server"
|
||||
done
|
||||
/etc/init.d/dnsmasq restart >/dev/null 2>&1
|
||||
restore_servers
|
||||
else
|
||||
/etc/init.d/dnsmasq restart >/dev/null 2>&1
|
||||
fi
|
||||
echolog "重启 dnsmasq 服务"
|
||||
LOG_FILE=${_LOG_FILE}
|
||||
}
|
||||
|
||||
restart() {
|
||||
local no_log
|
||||
eval_set_val $@
|
||||
_LOG_FILE=$LOG_FILE
|
||||
[ -n "$no_log" ] && LOG_FILE="/dev/null"
|
||||
/etc/init.d/dnsmasq restart >/dev/null 2>&1
|
||||
echolog "重启 dnsmasq 服务"
|
||||
LOG_FILE=${_LOG_FILE}
|
||||
}
|
||||
|
||||
gen_items() {
|
||||
local dnss settype setnames outf ipsetoutf
|
||||
eval_set_val $@
|
||||
|
||||
awk -v dnss="${dnss}" -v settype="${settype}" -v setnames="${setnames}" -v outf="${outf}" -v ipsetoutf="${ipsetoutf}" '
|
||||
BEGIN {
|
||||
if(outf == "") outf="/dev/stdout";
|
||||
if(ipsetoutf == "") ipsetoutf=outf;
|
||||
split(dnss, dns, ","); setdns=length(dns)>0; setlist=length(setnames)>0;
|
||||
if(setdns) for(i in dns) if(length(dns[i])==0) delete dns[i];
|
||||
fail=1;
|
||||
}
|
||||
! /^$/&&!/^#/ {
|
||||
fail=0
|
||||
if(setdns) for(i in dns) printf("server=/.%s/%s\n", $0, dns[i]) >>outf;
|
||||
if(setlist) printf("%s=/.%s/%s\n", settype, $0, setnames) >>ipsetoutf;
|
||||
}
|
||||
END {fflush(outf); close(outf); fflush(ipsetoutf); close(ipsetoutf); exit(fail);}
|
||||
'
|
||||
}
|
||||
|
||||
add() {
|
||||
local TMP_DNSMASQ_PATH DNSMASQ_CONF_FILE DEFAULT_DNS LOCAL_DNS TUN_DNS NFTFLAG NO_LOGIC_LOG
|
||||
eval_set_val $@
|
||||
_LOG_FILE=$LOG_FILE
|
||||
[ -n "$NO_LOGIC_LOG" ] && LOG_FILE="/dev/null"
|
||||
mkdir -p "${TMP_DNSMASQ_PATH}" "${DNSMASQ_PATH}" "/tmp/dnsmasq.d"
|
||||
|
||||
local set_type="ipset"
|
||||
[ "${NFTFLAG}" = "1" ] && {
|
||||
set_type="nftset"
|
||||
local setflag_4="4#inet#fw4#"
|
||||
local setflag_6="6#inet#fw4#"
|
||||
}
|
||||
|
||||
#始终用国内DNS解析节点域名
|
||||
servers=$(uci show "${CONFIG}" | grep ".address=" | cut -d "'" -f 2)
|
||||
hosts_foreach "servers" host_from_url | grep '[a-zA-Z]$' | sort -u | gen_items settype="${set_type}" setnames="${setflag_4}passwall2_vpslist,${setflag_6}passwall2_vpslist6" dnss="${LOCAL_DNS:-${DEFAULT_DNS}}" outf="${TMP_DNSMASQ_PATH}/10-vpslist_host.conf" ipsetoutf="${TMP_DNSMASQ_PATH}/ipset.conf"
|
||||
echolog " - [$?]节点列表中的域名(vpslist):${DEFAULT_DNS:-默认}"
|
||||
|
||||
echo "conf-dir=${TMP_DNSMASQ_PATH}" > $DNSMASQ_CONF_FILE
|
||||
[ -n "${TUN_DNS}" ] && {
|
||||
echo "${DEFAULT_DNS}" > $TMP_PATH/default_DNS
|
||||
cat <<-EOF >> $DNSMASQ_CONF_FILE
|
||||
server=${TUN_DNS}
|
||||
all-servers
|
||||
no-poll
|
||||
no-resolv
|
||||
EOF
|
||||
echolog " - [$?]默认:${TUN_DNS}"
|
||||
}
|
||||
LOG_FILE=${_LOG_FILE}
|
||||
}
|
||||
|
||||
del() {
|
||||
rm -rf /tmp/dnsmasq.d/dnsmasq-$CONFIG.conf
|
||||
rm -rf $DNSMASQ_PATH/dnsmasq-$CONFIG.conf
|
||||
rm -rf $TMP_DNSMASQ_PATH
|
||||
}
|
||||
|
||||
arg1=$1
|
||||
shift
|
||||
case $arg1 in
|
||||
stretch)
|
||||
stretch $@
|
||||
;;
|
||||
add)
|
||||
add $@
|
||||
;;
|
||||
del)
|
||||
del $@
|
||||
;;
|
||||
restart)
|
||||
restart $@
|
||||
;;
|
||||
logic_restart)
|
||||
logic_restart $@
|
||||
;;
|
||||
*) ;;
|
||||
esac
|
952
luci-app-passwall2/root/usr/share/passwall2/iptables.sh
Executable file
952
luci-app-passwall2/root/usr/share/passwall2/iptables.sh
Executable file
@ -0,0 +1,952 @@
|
||||
#!/bin/sh
|
||||
|
||||
DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
MY_PATH=$DIR/iptables.sh
|
||||
IPSET_LANLIST="passwall2_lanlist"
|
||||
IPSET_VPSLIST="passwall2_vpslist"
|
||||
IPSET_WHITELIST="passwall2_whitelist"
|
||||
|
||||
IPSET_LANLIST6="passwall2_lanlist6"
|
||||
IPSET_VPSLIST6="passwall2_vpslist6"
|
||||
IPSET_WHITELIST6="passwall2_whitelist6"
|
||||
|
||||
FORCE_INDEX=2
|
||||
|
||||
. /lib/functions/network.sh
|
||||
|
||||
ipt=$(command -v iptables-legacy || command -v iptables)
|
||||
ip6t=$(command -v ip6tables-legacy || command -v ip6tables)
|
||||
|
||||
ipt_n="$ipt -t nat -w"
|
||||
ipt_m="$ipt -t mangle -w"
|
||||
ip6t_n="$ip6t -t nat -w"
|
||||
ip6t_m="$ip6t -t mangle -w"
|
||||
[ -z "$ip6t" -o -z "$(lsmod | grep 'ip6table_nat')" ] && ip6t_n="eval #$ip6t_n"
|
||||
[ -z "$ip6t" -o -z "$(lsmod | grep 'ip6table_mangle')" ] && ip6t_m="eval #$ip6t_m"
|
||||
FWI=$(uci -q get firewall.passwall2.path 2>/dev/null)
|
||||
FAKE_IP="198.18.0.0/16"
|
||||
FAKE_IP_6="fc00::/18"
|
||||
|
||||
factor() {
|
||||
if [ -z "$1" ] || [ -z "$2" ]; then
|
||||
echo ""
|
||||
elif [ "$1" == "1:65535" ]; then
|
||||
echo ""
|
||||
else
|
||||
echo "$2 $1"
|
||||
fi
|
||||
}
|
||||
|
||||
dst() {
|
||||
echo "-m set $2 --match-set $1 dst"
|
||||
}
|
||||
|
||||
comment() {
|
||||
local name=$(echo $1 | sed 's/ /_/g')
|
||||
echo "-m comment --comment '$name'"
|
||||
}
|
||||
|
||||
destroy_ipset() {
|
||||
for i in "$@"; do
|
||||
ipset -q -F $i
|
||||
ipset -q -X $i
|
||||
done
|
||||
}
|
||||
|
||||
insert_rule_before() {
|
||||
[ $# -ge 3 ] || {
|
||||
return 1
|
||||
}
|
||||
local ipt_tmp="${1}"; shift
|
||||
local chain="${1}"; shift
|
||||
local keyword="${1}"; shift
|
||||
local rule="${1}"; shift
|
||||
local _index=$($ipt_tmp -n -L $chain --line-numbers 2>/dev/null | grep "$keyword" | head -n 1 | awk '{print $1}')
|
||||
$ipt_tmp -I $chain $_index $rule
|
||||
}
|
||||
|
||||
insert_rule_after() {
|
||||
[ $# -ge 3 ] || {
|
||||
return 1
|
||||
}
|
||||
local ipt_tmp="${1}"; shift
|
||||
local chain="${1}"; shift
|
||||
local keyword="${1}"; shift
|
||||
local rule="${1}"; shift
|
||||
local _index=$($ipt_tmp -n -L $chain --line-numbers 2>/dev/null | grep "$keyword" | awk 'END {print}' | awk '{print $1}')
|
||||
_index=${_index:-0}
|
||||
_index=$((_index + 1))
|
||||
$ipt_tmp -I $chain $_index $rule
|
||||
}
|
||||
|
||||
RULE_LAST_INDEX() {
|
||||
[ $# -ge 3 ] || {
|
||||
echolog "索引列举方式不正确(iptables),终止执行!"
|
||||
return 1
|
||||
}
|
||||
local ipt_tmp="${1}"; shift
|
||||
local chain="${1}"; shift
|
||||
local list="${1}"; shift
|
||||
local default="${1:-0}"; shift
|
||||
local _index=$($ipt_tmp -n -L $chain --line-numbers 2>/dev/null | grep "$list" | head -n 1 | awk '{print $1}')
|
||||
echo "${_index:-${default}}"
|
||||
}
|
||||
|
||||
REDIRECT() {
|
||||
local s="-j REDIRECT"
|
||||
[ -n "$1" ] && {
|
||||
local s="$s --to-ports $1"
|
||||
[ "$2" == "MARK" ] && s="-j MARK --set-mark $1"
|
||||
[ "$2" == "TPROXY" ] && {
|
||||
local mark="-m mark --mark 1"
|
||||
s="${mark} -j TPROXY --tproxy-mark 0x1/0x1 --on-port $1"
|
||||
}
|
||||
}
|
||||
echo $s
|
||||
}
|
||||
|
||||
get_redirect_ipt() {
|
||||
echo "$(REDIRECT $2 $3)"
|
||||
}
|
||||
|
||||
get_redirect_ip6t() {
|
||||
echo "$(REDIRECT $2 $3)"
|
||||
}
|
||||
|
||||
get_action_chain_name() {
|
||||
echo "全局代理"
|
||||
}
|
||||
|
||||
gen_lanlist() {
|
||||
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.168.0.0/16
|
||||
224.0.0.0/4
|
||||
240.0.0.0/4
|
||||
EOF
|
||||
}
|
||||
|
||||
gen_lanlist_6() {
|
||||
cat <<-EOF
|
||||
::/128
|
||||
::1/128
|
||||
::ffff:0:0/96
|
||||
::ffff:0:0:0/96
|
||||
64:ff9b::/96
|
||||
100::/64
|
||||
2001::/32
|
||||
2001:20::/28
|
||||
2001:db8::/32
|
||||
2002::/16
|
||||
fc00::/7
|
||||
fe80::/10
|
||||
ff00::/8
|
||||
EOF
|
||||
}
|
||||
|
||||
get_wan_ip() {
|
||||
local NET_IF
|
||||
local NET_ADDR
|
||||
|
||||
network_flush_cache
|
||||
network_find_wan NET_IF
|
||||
network_get_ipaddr NET_ADDR "${NET_IF}"
|
||||
|
||||
echo $NET_ADDR
|
||||
}
|
||||
|
||||
get_wan6_ip() {
|
||||
local NET_IF
|
||||
local NET_ADDR
|
||||
|
||||
network_flush_cache
|
||||
network_find_wan6 NET_IF
|
||||
network_get_ipaddr6 NET_ADDR "${NET_IF}"
|
||||
|
||||
echo $NET_ADDR
|
||||
}
|
||||
|
||||
load_acl() {
|
||||
[ "$ENABLED_ACLS" == 1 ] && {
|
||||
acl_app
|
||||
echolog "访问控制:"
|
||||
for sid in $(ls -F ${TMP_ACL_PATH} | grep '/$' | awk -F '/' '{print $1}'); do
|
||||
eval $(uci -q show "${CONFIG}.${sid}" | cut -d'.' -sf 3-)
|
||||
|
||||
tcp_no_redir_ports=${tcp_no_redir_ports:-default}
|
||||
udp_no_redir_ports=${udp_no_redir_ports:-default}
|
||||
tcp_proxy_mode="global"
|
||||
udp_proxy_mode="global"
|
||||
node=${node:-default}
|
||||
[ "$tcp_no_redir_ports" = "default" ] && tcp_no_redir_ports=$TCP_NO_REDIR_PORTS
|
||||
[ "$udp_no_redir_ports" = "default" ] && udp_no_redir_ports=$UDP_NO_REDIR_PORTS
|
||||
[ "$tcp_redir_ports" = "default" ] && tcp_redir_ports=$TCP_REDIR_PORTS
|
||||
[ "$udp_redir_ports" = "default" ] && udp_redir_ports=$UDP_REDIR_PORTS
|
||||
|
||||
node_remark=$(config_n_get $NODE remarks)
|
||||
[ -s "${TMP_ACL_PATH}/${sid}/var_node" ] && node=$(cat ${TMP_ACL_PATH}/${sid}/var_node)
|
||||
[ -s "${TMP_ACL_PATH}/${sid}/var_port" ] && redir_port=$(cat ${TMP_ACL_PATH}/${sid}/var_port)
|
||||
[ -n "$node" ] && [ "$node" != "default" ] && node_remark=$(config_n_get $node remarks)
|
||||
|
||||
for i in $(cat ${TMP_ACL_PATH}/${sid}/rule_list); do
|
||||
if [ -n "$(echo ${i} | grep '^iprange:')" ]; then
|
||||
_iprange=$(echo ${i} | sed 's#iprange:##g')
|
||||
_ipt_source=$(factor ${_iprange} "-m iprange --src-range")
|
||||
msg="备注【$remarks】,IP range【${_iprange}】,"
|
||||
elif [ -n "$(echo ${i} | grep '^ipset:')" ]; then
|
||||
_ipset=$(echo ${i} | sed 's#ipset:##g')
|
||||
_ipt_source="-m set --match-set ${_ipset} src"
|
||||
msg="备注【$remarks】,IPset【${_ipset}】,"
|
||||
elif [ -n "$(echo ${i} | grep '^ip:')" ]; then
|
||||
_ip=$(echo ${i} | sed 's#ip:##g')
|
||||
_ipt_source=$(factor ${_ip} "-s")
|
||||
msg="备注【$remarks】,IP【${_ip}】,"
|
||||
elif [ -n "$(echo ${i} | grep '^mac:')" ]; then
|
||||
_mac=$(echo ${i} | sed 's#mac:##g')
|
||||
_ipt_source=$(factor ${_mac} "-m mac --mac-source")
|
||||
msg="备注【$remarks】,MAC【${_mac}】,"
|
||||
else
|
||||
continue
|
||||
fi
|
||||
|
||||
ipt_tmp=$ipt_n
|
||||
[ -n "${is_tproxy}" ] && ipt_tmp=$ipt_m
|
||||
|
||||
[ -n "$redir_port" ] && {
|
||||
if [ "$tcp_proxy_mode" != "disable" ]; then
|
||||
[ -s "${TMP_ACL_PATH}/${sid}/var_redirect_dns_port" ] && $ipt_n -A PSW2_REDIRECT $(comment "$remarks") -p udp ${_ipt_source} --dport 53 -j REDIRECT --to-ports $(cat ${TMP_ACL_PATH}/${sid}/var_redirect_dns_port)
|
||||
msg2="${msg}使用TCP节点[$node_remark] [$(get_action_chain_name $tcp_proxy_mode)]"
|
||||
if [ -n "${is_tproxy}" ]; then
|
||||
msg2="${msg2}(TPROXY:${redir_port})代理"
|
||||
ipt_tmp=$ipt_m
|
||||
else
|
||||
msg2="${msg2}(REDIRECT:${redir_port})代理"
|
||||
fi
|
||||
|
||||
[ "$accept_icmp" = "1" ] && {
|
||||
$ipt_n -A PSW2 $(comment "$remarks") -p icmp ${_ipt_source} -d $FAKE_IP $(REDIRECT)
|
||||
$ipt_n -A PSW2 $(comment "$remarks") -p icmp ${_ipt_source} $(REDIRECT)
|
||||
}
|
||||
|
||||
[ "$accept_icmpv6" = "1" ] && [ "$PROXY_IPV6" == "1" ] && {
|
||||
$ip6t_n -A PSW2 $(comment "$remarks") -p ipv6-icmp ${_ipt_source} -d $FAKE_IP_6 $(REDIRECT) 2>/dev/null
|
||||
$ip6t_n -A PSW2 $(comment "$remarks") -p ipv6-icmp ${_ipt_source} $(REDIRECT) 2>/dev/null
|
||||
}
|
||||
|
||||
[ "$tcp_no_redir_ports" != "disable" ] && {
|
||||
$ipt_tmp -A PSW2 $(comment "$remarks") ${_ipt_source} -p tcp -m multiport --dport $tcp_no_redir_ports -j RETURN
|
||||
$ip6t_m -A PSW2 $(comment "$remarks") ${_ipt_source} -p tcp -m multiport --dport $tcp_no_redir_ports -j RETURN 2>/dev/null
|
||||
msg2="${msg2}[$?]除${tcp_no_redir_ports}外的"
|
||||
}
|
||||
msg2="${msg2}所有端口"
|
||||
|
||||
if [ "${ipt_tmp}" = "${ipt_n}" ]; then
|
||||
$ipt_n -A PSW2 $(comment "$remarks") -p tcp ${_ipt_source} -d $FAKE_IP $(REDIRECT $redir_port)
|
||||
$ipt_n -A PSW2 $(comment "$remarks") -p tcp ${_ipt_source} $(factor $tcp_redir_ports "-m multiport --dport") $(REDIRECT $redir_port)
|
||||
else
|
||||
$ipt_m -A PSW2 $(comment "$remarks") -p tcp ${_ipt_source} -d $FAKE_IP -j PSW2_RULE
|
||||
$ipt_m -A PSW2 $(comment "$remarks") -p tcp ${_ipt_source} $(factor $tcp_redir_ports "-m multiport --dport") -j PSW2_RULE
|
||||
$ipt_m -A PSW2 $(comment "$remarks") -p tcp ${_ipt_source} $(REDIRECT $redir_port TPROXY)
|
||||
fi
|
||||
[ "$PROXY_IPV6" == "1" ] && {
|
||||
$ip6t_m -A PSW2 $(comment "$remarks") -p tcp ${_ipt_source} -d $FAKE_IP_6 -j PSW2_RULE 2>/dev/null
|
||||
$ip6t_m -A PSW2 $(comment "$remarks") -p tcp ${_ipt_source} $(factor $tcp_redir_ports "-m multiport --dport") -j PSW2_RULE 2>/dev/null
|
||||
$ip6t_m -A PSW2 $(comment "$remarks") -p tcp ${_ipt_source} $(REDIRECT $redir_port TPROXY) 2>/dev/null
|
||||
}
|
||||
else
|
||||
msg2="${msg}不代理TCP"
|
||||
fi
|
||||
echolog " - ${msg2}"
|
||||
}
|
||||
|
||||
$ipt_tmp -A PSW2 $(comment "$remarks") ${_ipt_source} -p tcp -j RETURN
|
||||
$ip6t_m -A PSW2 $(comment "$remarks") ${_ipt_source} -p tcp -j RETURN 2>/dev/null
|
||||
|
||||
[ -n "$redir_port" ] && {
|
||||
if [ "$udp_proxy_mode" != "disable" ]; then
|
||||
msg2="${msg}使用UDP节点[$node_remark] [$(get_action_chain_name $udp_proxy_mode)]"
|
||||
msg2="${msg2}(TPROXY:${redir_port})代理"
|
||||
[ "$udp_no_redir_ports" != "disable" ] && {
|
||||
$ipt_m -A PSW2 $(comment "$remarks") ${_ipt_source} -p udp -m multiport --dport $udp_no_redir_ports -j RETURN
|
||||
$ip6t_m -A PSW2 $(comment "$remarks") ${_ipt_source} -p udp -m multiport --dport $udp_no_redir_ports -j RETURN 2>/dev/null
|
||||
msg2="${msg2}[$?]除${udp_no_redir_ports}外的"
|
||||
}
|
||||
msg2="${msg2}所有端口"
|
||||
|
||||
$ipt_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source} -d $FAKE_IP -j PSW2_RULE
|
||||
$ipt_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source} $(factor $udp_redir_ports "-m multiport --dport") -j PSW2_RULE
|
||||
$ipt_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source} $(REDIRECT $redir_port TPROXY)
|
||||
|
||||
[ "$PROXY_IPV6" == "1" ] && [ "$PROXY_IPV6_UDP" == "1" ] && {
|
||||
$ip6t_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source} -d $FAKE_IP_6 -j PSW2_RULE 2>/dev/null
|
||||
$ip6t_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source} $(factor $udp_redir_ports "-m multiport --dport") -j PSW2_RULE 2>/dev/null
|
||||
$ip6t_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source} $(REDIRECT $redir_port TPROXY) 2>/dev/null
|
||||
}
|
||||
else
|
||||
msg2="${msg}不代理UDP"
|
||||
fi
|
||||
echolog " - ${msg2}"
|
||||
}
|
||||
$ipt_m -A PSW2 $(comment "$remarks") ${_ipt_source} -p udp -j RETURN
|
||||
$ip6t_m -A PSW2 $(comment "$remarks") ${_ipt_source} -p udp -j RETURN 2>/dev/null
|
||||
done
|
||||
unset enabled sid remarks sources tcp_no_redir_ports udp_no_redir_ports tcp_redir_ports udp_redir_ports node
|
||||
unset _ip _mac _iprange _ipset _ip_or_mac rule_list node_remark
|
||||
unset ipt_tmp msg msg2
|
||||
done
|
||||
}
|
||||
|
||||
[ "$ENABLED_DEFAULT_ACL" == 1 ] && {
|
||||
# 加载默认代理模式
|
||||
if [ "$TCP_PROXY_MODE" != "disable" ]; then
|
||||
local ipt_tmp=$ipt_n
|
||||
[ -n "${is_tproxy}" ] && ipt_tmp=$ipt_m
|
||||
[ "$TCP_NO_REDIR_PORTS" != "disable" ] && {
|
||||
$ipt_tmp -A PSW2 $(comment "默认") -p tcp -m multiport --dport $TCP_NO_REDIR_PORTS -j RETURN
|
||||
$ip6t_m -A PSW2 $(comment "默认") -p tcp -m multiport --dport $TCP_NO_REDIR_PORTS -j RETURN
|
||||
msg="${msg}除${TCP_NO_REDIR_PORTS}外的"
|
||||
}
|
||||
[ "$NODE" != "nil" ] && {
|
||||
msg="TCP默认代理:使用节点[$(config_n_get $NODE remarks)] [$(get_action_chain_name $TCP_PROXY_MODE)]"
|
||||
if [ -n "${is_tproxy}" ]; then
|
||||
msg="${msg}(TPROXY:${REDIR_PORT})代理"
|
||||
else
|
||||
msg="${msg}(REDIRECT:${REDIR_PORT})代理"
|
||||
fi
|
||||
|
||||
[ "$TCP_NO_REDIR_PORTS" != "disable" ] && msg="${msg}除${TCP_NO_REDIR_PORTS}外的"
|
||||
msg="${msg}所有端口"
|
||||
|
||||
[ "$accept_icmp" = "1" ] && {
|
||||
$ipt_n -A PSW2 $(comment "默认") -p icmp -d $FAKE_IP $(REDIRECT)
|
||||
$ipt_n -A PSW2 $(comment "默认") -p icmp $(REDIRECT)
|
||||
}
|
||||
|
||||
[ "$accept_icmpv6" = "1" ] && [ "$PROXY_IPV6" == "1" ] && {
|
||||
$ip6t_n -A PSW2 $(comment "默认") -p ipv6-icmp -d $FAKE_IP_6 $(REDIRECT)
|
||||
$ip6t_n -A PSW2 $(comment "默认") -p ipv6-icmp $(REDIRECT)
|
||||
}
|
||||
|
||||
if [ "${ipt_tmp}" = "${ipt_n}" ]; then
|
||||
$ipt_n -A PSW2 $(comment "默认") -p tcp -d $FAKE_IP $(REDIRECT $REDIR_PORT)
|
||||
$ipt_n -A PSW2 $(comment "默认") -p tcp $(factor $TCP_REDIR_PORTS "-m multiport --dport") $(REDIRECT $REDIR_PORT)
|
||||
else
|
||||
$ipt_m -A PSW2 $(comment "默认") -p tcp -d $FAKE_IP -j PSW2_RULE
|
||||
$ipt_m -A PSW2 $(comment "默认") -p tcp $(factor $TCP_REDIR_PORTS "-m multiport --dport") -j PSW2_RULE
|
||||
$ipt_m -A PSW2 $(comment "默认") -p tcp $(REDIRECT $REDIR_PORT TPROXY)
|
||||
fi
|
||||
|
||||
[ "$PROXY_IPV6" == "1" ] && {
|
||||
$ip6t_m -A PSW2 $(comment "默认") -p tcp -d $FAKE_IP_6 -j PSW2_RULE
|
||||
$ip6t_m -A PSW2 $(comment "默认") -p tcp $(factor $TCP_REDIR_PORTS "-m multiport --dport") -j PSW2_RULE
|
||||
$ip6t_m -A PSW2 $(comment "默认") -p tcp $(REDIRECT $REDIR_PORT TPROXY)
|
||||
}
|
||||
|
||||
echolog "${msg}"
|
||||
}
|
||||
fi
|
||||
$ipt_n -A PSW2 $(comment "默认") -p tcp -j RETURN
|
||||
$ipt_m -A PSW2 $(comment "默认") -p tcp -j RETURN
|
||||
$ip6t_m -A PSW2 $(comment "默认") -p tcp -j RETURN
|
||||
|
||||
# 加载UDP默认代理模式
|
||||
if [ "$UDP_PROXY_MODE" != "disable" ]; then
|
||||
[ "$UDP_NO_REDIR_PORTS" != "disable" ] && {
|
||||
$ipt_m -A PSW2 $(comment "默认") -p udp -m multiport --dport $UDP_NO_REDIR_PORTS -j RETURN
|
||||
$ip6t_m -A PSW2 $(comment "默认") -p udp -m multiport --dport $UDP_NO_REDIR_PORTS -j RETURN
|
||||
}
|
||||
|
||||
[ -n "1" ] && {
|
||||
msg="UDP默认代理:使用节点[$(config_n_get $NODE remarks)] [$(get_action_chain_name $UDP_PROXY_MODE)](TPROXY:${REDIR_PORT})代理"
|
||||
|
||||
[ "$UDP_NO_REDIR_PORTS" != "disable" ] && msg="${msg}除${UDP_NO_REDIR_PORTS}外的"
|
||||
msg="${msg}所有端口"
|
||||
|
||||
$ipt_m -A PSW2 $(comment "默认") -p udp -d $FAKE_IP -j PSW2_RULE
|
||||
$ipt_m -A PSW2 $(comment "默认") -p udp $(factor $UDP_REDIR_PORTS "-m multiport --dport") -j PSW2_RULE
|
||||
$ipt_m -A PSW2 $(comment "默认") -p udp $(REDIRECT $REDIR_PORT TPROXY)
|
||||
|
||||
if [ "$PROXY_IPV6_UDP" == "1" ]; then
|
||||
$ip6t_m -A PSW2 $(comment "默认") -p udp -d $FAKE_IP_6 -j PSW2_RULE
|
||||
$ip6t_m -A PSW2 $(comment "默认") -p udp $(factor $UDP_REDIR_PORTS "-m multiport --dport") -j PSW2_RULE
|
||||
$ip6t_m -A PSW2 $(comment "默认") -p udp $(REDIRECT $REDIR_PORT TPROXY)
|
||||
fi
|
||||
|
||||
echolog "${msg}"
|
||||
}
|
||||
fi
|
||||
$ipt_m -A PSW2 $(comment "默认") -p udp -j RETURN
|
||||
$ip6t_m -A PSW2 $(comment "默认") -p udp -j RETURN
|
||||
}
|
||||
}
|
||||
|
||||
filter_haproxy() {
|
||||
for item in $(uci show $CONFIG | grep ".lbss=" | cut -d "'" -f 2); do
|
||||
local ip=$(get_host_ip ipv4 $(echo $item | awk -F ":" '{print $1}') 1)
|
||||
[ -n "$ip" ] && ipset -q add $IPSET_VPSLIST $ip
|
||||
done
|
||||
echolog "加入负载均衡的节点到ipset[$IPSET_VPSLIST]直连完成"
|
||||
}
|
||||
|
||||
filter_vpsip() {
|
||||
uci show $CONFIG | grep ".address=" | cut -d "'" -f 2 | grep -E "([0-9]{1,3}[\.]){3}[0-9]{1,3}" | sed -e "/^$/d" | sed -e "s/^/add $IPSET_VPSLIST &/g" | awk '{print $0} END{print "COMMIT"}' | ipset -! -R
|
||||
uci show $CONFIG | grep ".address=" | cut -d "'" -f 2 | grep -E "([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4}" | sed -e "/^$/d" | sed -e "s/^/add $IPSET_VPSLIST6 &/g" | awk '{print $0} END{print "COMMIT"}' | ipset -! -R
|
||||
echolog "加入所有节点到ipset[$IPSET_VPSLIST]直连完成"
|
||||
}
|
||||
|
||||
filter_node() {
|
||||
local proxy_node=${1}
|
||||
local stream=$(echo ${2} | tr 'A-Z' 'a-z')
|
||||
local proxy_port=${3}
|
||||
|
||||
filter_rules() {
|
||||
local node=${1}
|
||||
local stream=${2}
|
||||
local _proxy=${3}
|
||||
local _port=${4}
|
||||
local _is_tproxy ipt_tmp msg msg2
|
||||
|
||||
if [ -n "$node" ] && [ "$node" != "nil" ]; then
|
||||
local type=$(echo $(config_n_get $node type) | tr 'A-Z' 'a-z')
|
||||
local address=$(config_n_get $node address)
|
||||
local port=$(config_n_get $node port)
|
||||
ipt_tmp=$ipt_n
|
||||
_is_tproxy=${is_tproxy}
|
||||
[ "$stream" == "udp" ] && _is_tproxy="TPROXY"
|
||||
if [ -n "${_is_tproxy}" ]; then
|
||||
ipt_tmp=$ipt_m
|
||||
msg="TPROXY"
|
||||
else
|
||||
msg="REDIRECT"
|
||||
fi
|
||||
else
|
||||
echolog " - 节点配置不正常,略过"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local ADD_INDEX=$FORCE_INDEX
|
||||
for _ipt in 4 6; do
|
||||
[ "$_ipt" == "4" ] && _ipt=$ipt_tmp
|
||||
[ "$_ipt" == "6" ] && _ipt=$ip6t_m
|
||||
$_ipt -n -L PSW2_OUTPUT | grep -q "${address}:${port}"
|
||||
if [ $? -ne 0 ]; then
|
||||
unset dst_rule
|
||||
local dst_rule="-j PSW2_RULE"
|
||||
msg2="按规则路由(${msg})"
|
||||
[ "$_ipt" == "$ipt_m" -o "$_ipt" == "$ip6t_m" ] || {
|
||||
dst_rule=$(REDIRECT $_port)
|
||||
msg2="套娃使用(${msg}:${port} -> ${_port})"
|
||||
}
|
||||
[ -n "$_proxy" ] && [ "$_proxy" == "1" ] && [ -n "$_port" ] || {
|
||||
ADD_INDEX=$(RULE_LAST_INDEX "$_ipt" PSW2_OUTPUT "$IPSET_VPSLIST" $FORCE_INDEX)
|
||||
dst_rule=" -j RETURN"
|
||||
msg2="直连代理"
|
||||
}
|
||||
$_ipt -I PSW2_OUTPUT $ADD_INDEX $(comment "${address}:${port}") -p $stream -d $address --dport $port $dst_rule 2>/dev/null
|
||||
else
|
||||
msg2="已配置过的节点,"
|
||||
fi
|
||||
done
|
||||
msg="[$?]$(echo ${2} | tr 'a-z' 'A-Z')${msg2}使用链${ADD_INDEX},节点(${type}):${address}:${port}"
|
||||
#echolog " - ${msg}"
|
||||
}
|
||||
|
||||
local proxy_protocol=$(config_n_get $proxy_node protocol)
|
||||
local proxy_type=$(echo $(config_n_get $proxy_node type nil) | tr 'A-Z' 'a-z')
|
||||
[ "$proxy_type" == "nil" ] && echolog " - 节点配置不正常,略过!:${proxy_node}" && return 0
|
||||
if [ "$proxy_protocol" == "_balancing" ]; then
|
||||
#echolog " - 多节点负载均衡(${proxy_type})..."
|
||||
proxy_node=$(config_n_get $proxy_node balancing_node)
|
||||
for _node in $proxy_node; do
|
||||
filter_rules "$_node" "$stream"
|
||||
done
|
||||
elif [ "$proxy_protocol" == "_shunt" ]; then
|
||||
#echolog " - 按请求目的地址分流(${proxy_type})..."
|
||||
local default_node=$(config_n_get $proxy_node default_node _direct)
|
||||
local main_node=$(config_n_get $proxy_node main_node nil)
|
||||
if [ "$main_node" != "nil" ]; then
|
||||
filter_rules $main_node $stream
|
||||
else
|
||||
if [ "$default_node" != "_direct" ] && [ "$default_node" != "_blackhole" ]; then
|
||||
filter_rules $default_node $stream
|
||||
fi
|
||||
fi
|
||||
:<<!
|
||||
local default_node_address=$(get_host_ip ipv4 $(config_n_get $default_node address) 1)
|
||||
local default_node_port=$(config_n_get $default_node port)
|
||||
|
||||
local shunt_ids=$(uci show $CONFIG | grep "=shunt_rules" | awk -F '.' '{print $2}' | awk -F '=' '{print $1}')
|
||||
for shunt_id in $shunt_ids; do
|
||||
#local shunt_proxy=$(config_n_get $proxy_node "${shunt_id}_proxy" 0)
|
||||
local shunt_proxy=0
|
||||
local shunt_node=$(config_n_get $proxy_node "${shunt_id}" nil)
|
||||
[ "$shunt_node" != "nil" ] && {
|
||||
[ "$shunt_proxy" == 1 ] && {
|
||||
local shunt_node_address=$(get_host_ip ipv4 $(config_n_get $shunt_node address) 1)
|
||||
local shunt_node_port=$(config_n_get $shunt_node port)
|
||||
[ "$shunt_node_address" == "$default_node_address" ] && [ "$shunt_node_port" == "$default_node_port" ] && {
|
||||
shunt_proxy=0
|
||||
}
|
||||
}
|
||||
filter_rules "$(config_n_get $proxy_node $shunt_id)" "$stream" "$shunt_proxy" "$proxy_port"
|
||||
}
|
||||
done
|
||||
!
|
||||
else
|
||||
#echolog " - 普通节点(${proxy_type})..."
|
||||
filter_rules "$proxy_node" "$stream"
|
||||
fi
|
||||
}
|
||||
|
||||
dns_hijack() {
|
||||
$ipt_n -I PSW2 -p udp --dport 53 -j REDIRECT --to-ports 53
|
||||
echolog "强制转发本机DNS端口 UDP/53 的请求[$?]"
|
||||
}
|
||||
|
||||
add_firewall_rule() {
|
||||
echolog "开始加载防火墙规则..."
|
||||
ipset -! create $IPSET_LANLIST nethash maxelem 1048576
|
||||
ipset -! create $IPSET_VPSLIST nethash maxelem 1048576
|
||||
ipset -! create $IPSET_WHITELIST nethash maxelem 1048576
|
||||
|
||||
ipset -! create $IPSET_LANLIST6 nethash family inet6 maxelem 1048576
|
||||
ipset -! create $IPSET_VPSLIST6 nethash family inet6 maxelem 1048576
|
||||
ipset -! create $IPSET_WHITELIST6 nethash family inet6 maxelem 1048576
|
||||
|
||||
ipset -! -R <<-EOF
|
||||
$(gen_lanlist | sed -e "s/^/add $IPSET_LANLIST /")
|
||||
EOF
|
||||
|
||||
ipset -! -R <<-EOF
|
||||
$(gen_lanlist_6 | sed -e "s/^/add $IPSET_LANLIST6 /")
|
||||
EOF
|
||||
|
||||
# 忽略特殊IP段
|
||||
local lan_ifname lan_ip
|
||||
lan_ifname=$(uci -q -p /tmp/state get network.lan.ifname)
|
||||
[ -n "$lan_ifname" ] && {
|
||||
lan_ip=$(ip address show $lan_ifname | grep -w "inet" | awk '{print $2}')
|
||||
lan_ip6=$(ip address show $lan_ifname | grep -w "inet6" | awk '{print $2}')
|
||||
#echolog "本机IPv4网段互访直连:${lan_ip}"
|
||||
#echolog "本机IPv6网段互访直连:${lan_ip6}"
|
||||
|
||||
[ -n "$lan_ip" ] && ipset -! -R <<-EOF
|
||||
$(echo $lan_ip | sed -e "s/ /\n/g" | sed -e "s/^/add $IPSET_LANLIST /")
|
||||
EOF
|
||||
|
||||
[ -n "$lan_ip6" ] && ipset -! -R <<-EOF
|
||||
$(echo $lan_ip6 | sed -e "s/ /\n/g" | sed -e "s/^/add $IPSET_LANLIST6 /")
|
||||
EOF
|
||||
}
|
||||
|
||||
[ -n "$ISP_DNS" ] && {
|
||||
#echolog "处理 ISP DNS 例外..."
|
||||
for ispip in $ISP_DNS; do
|
||||
ipset -! add $IPSET_LANLIST $ispip >/dev/null 2>&1 &
|
||||
#echolog " - 追加到白名单:${ispip}"
|
||||
done
|
||||
}
|
||||
|
||||
[ -n "$ISP_DNS6" ] && {
|
||||
#echolog "处理 ISP IPv6 DNS 例外..."
|
||||
for ispip6 in $ISP_DNS6; do
|
||||
ipset -! add $IPSET_LANLIST6 $ispip6 >/dev/null 2>&1 &
|
||||
#echolog " - 追加到白名单:${ispip6}"
|
||||
done
|
||||
}
|
||||
|
||||
# 过滤所有节点IP
|
||||
filter_vpsip > /dev/null 2>&1 &
|
||||
filter_haproxy > /dev/null 2>&1 &
|
||||
|
||||
accept_icmp=$(config_t_get global_forwarding accept_icmp 0)
|
||||
accept_icmpv6=$(config_t_get global_forwarding accept_icmpv6 0)
|
||||
|
||||
local tcp_proxy_way=$(config_t_get global_forwarding tcp_proxy_way redirect)
|
||||
if [ "$tcp_proxy_way" = "redirect" ]; then
|
||||
unset is_tproxy
|
||||
elif [ "$tcp_proxy_way" = "tproxy" ]; then
|
||||
is_tproxy="TPROXY"
|
||||
fi
|
||||
|
||||
$ipt_n -N PSW2
|
||||
$ipt_n -A PSW2 $(dst $IPSET_LANLIST) -j RETURN
|
||||
$ipt_n -A PSW2 $(dst $IPSET_VPSLIST) -j RETURN
|
||||
$ipt_n -A PSW2 $(dst $IPSET_WHITELIST) ! -d $FAKE_IP -j RETURN
|
||||
|
||||
WAN_IP=$(get_wan_ip)
|
||||
[ ! -z "${WAN_IP}" ] && $ipt_n -A PSW2 $(comment "WAN_IP_RETURN") -d "${WAN_IP}" -j RETURN
|
||||
|
||||
[ "$accept_icmp" = "1" ] && insert_rule_after "$ipt_n" "PREROUTING" "prerouting_rule" "-p icmp -j PSW2"
|
||||
[ -z "${is_tproxy}" ] && insert_rule_after "$ipt_n" "PREROUTING" "prerouting_rule" "-p tcp -j PSW2"
|
||||
|
||||
$ipt_n -N PSW2_OUTPUT
|
||||
$ipt_n -A PSW2_OUTPUT $(dst $IPSET_LANLIST) -j RETURN
|
||||
$ipt_n -A PSW2_OUTPUT $(dst $IPSET_VPSLIST) -j RETURN
|
||||
$ipt_n -A PSW2_OUTPUT $(dst $IPSET_WHITELIST) ! -d $FAKE_IP -j RETURN
|
||||
$ipt_n -A PSW2_OUTPUT -m mark --mark 0xff -j RETURN
|
||||
|
||||
$ipt_n -N PSW2_REDIRECT
|
||||
$ipt_n -I PREROUTING 1 -j PSW2_REDIRECT
|
||||
|
||||
$ipt_m -N PSW2_DIVERT
|
||||
$ipt_m -A PSW2_DIVERT -j MARK --set-mark 1
|
||||
$ipt_m -A PSW2_DIVERT -j ACCEPT
|
||||
|
||||
$ipt_m -N PSW2_RULE
|
||||
$ipt_m -A PSW2_RULE -j CONNMARK --restore-mark
|
||||
$ipt_m -A PSW2_RULE -m mark --mark 0x1 -j RETURN
|
||||
$ipt_m -A PSW2_RULE -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -j MARK --set-xmark 1
|
||||
$ipt_m -A PSW2_RULE -p udp -m conntrack --ctstate NEW -j MARK --set-xmark 1
|
||||
$ipt_m -A PSW2_RULE -j CONNMARK --save-mark
|
||||
|
||||
$ipt_m -N PSW2
|
||||
$ipt_m -A PSW2 $(dst $IPSET_LANLIST) -j RETURN
|
||||
$ipt_m -A PSW2 $(dst $IPSET_VPSLIST) -j RETURN
|
||||
$ipt_m -A PSW2 $(dst $IPSET_WHITELIST) ! -d $FAKE_IP -j RETURN
|
||||
|
||||
[ ! -z "${WAN_IP}" ] && $ipt_m -A PSW2 $(comment "WAN_IP_RETURN") -d "${WAN_IP}" -j RETURN
|
||||
unset WAN_IP
|
||||
|
||||
insert_rule_before "$ipt_m" "PREROUTING" "mwan3" "-j PSW2"
|
||||
insert_rule_before "$ipt_m" "PREROUTING" "PSW2" "-p tcp -m socket -j PSW2_DIVERT"
|
||||
|
||||
$ipt_m -N PSW2_OUTPUT
|
||||
$ipt_m -A PSW2_OUTPUT -m mark --mark 0xff -j RETURN
|
||||
$ipt_m -A PSW2_OUTPUT $(dst $IPSET_LANLIST) -j RETURN
|
||||
$ipt_m -A PSW2_OUTPUT $(dst $IPSET_VPSLIST) -j RETURN
|
||||
$ipt_m -A PSW2_OUTPUT $(dst $IPSET_WHITELIST) ! -d $FAKE_IP -j RETURN
|
||||
|
||||
ip rule add fwmark 1 lookup 100
|
||||
ip route add local 0.0.0.0/0 dev lo table 100
|
||||
|
||||
[ "$accept_icmpv6" = "1" ] && {
|
||||
$ip6t_n -N PSW2
|
||||
$ip6t_n -A PSW2 $(dst $IPSET_LANLIST6) -j RETURN
|
||||
$ip6t_n -A PSW2 $(dst $IPSET_VPSLIST6) -j RETURN
|
||||
$ip6t_n -A PSW2 $(dst $IPSET_WHITELIST6) ! -d $FAKE_IP_6 -j RETURN
|
||||
$ip6t_n -A PREROUTING -p ipv6-icmp -j PSW2
|
||||
|
||||
$ip6t_n -N PSW2_OUTPUT
|
||||
$ip6t_n -A PSW2_OUTPUT $(dst $IPSET_LANLIST6) -j RETURN
|
||||
$ip6t_n -A PSW2_OUTPUT $(dst $IPSET_VPSLIST6) -j RETURN
|
||||
$ip6t_n -A PSW2_OUTPUT $(dst $IPSET_WHITELIST6) ! -d $FAKE_IP_6 -j RETURN
|
||||
$ip6t_n -A PSW2_OUTPUT -m mark --mark 0xff -j RETURN
|
||||
}
|
||||
|
||||
$ip6t_m -N PSW2_DIVERT
|
||||
$ip6t_m -A PSW2_DIVERT -j MARK --set-mark 1
|
||||
$ip6t_m -A PSW2_DIVERT -j ACCEPT
|
||||
|
||||
$ip6t_m -N PSW2_RULE
|
||||
$ip6t_m -A PSW2_RULE -j CONNMARK --restore-mark
|
||||
$ip6t_m -A PSW2_RULE -m mark --mark 0x1 -j RETURN
|
||||
$ip6t_m -A PSW2_RULE -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -j MARK --set-xmark 1
|
||||
$ip6t_m -A PSW2_RULE -p udp -m conntrack --ctstate NEW -j MARK --set-xmark 1
|
||||
$ip6t_m -A PSW2_RULE -j CONNMARK --save-mark
|
||||
|
||||
$ip6t_m -N PSW2
|
||||
$ip6t_m -A PSW2 $(dst $IPSET_LANLIST6) -j RETURN
|
||||
$ip6t_m -A PSW2 $(dst $IPSET_VPSLIST6) -j RETURN
|
||||
$ip6t_m -A PSW2 $(dst $IPSET_WHITELIST6) ! -d $FAKE_IP_6 -j RETURN
|
||||
|
||||
WAN6_IP=$(get_wan6_ip)
|
||||
[ ! -z "${WAN6_IP}" ] && $ip6t_m -A PSW2 $(comment "WAN6_IP_RETURN") -d ${WAN6_IP} -j RETURN
|
||||
unset WAN6_IP
|
||||
|
||||
insert_rule_before "$ip6t_m" "PREROUTING" "mwan3" "-j PSW2"
|
||||
insert_rule_before "$ip6t_m" "PREROUTING" "PSW2" "-p tcp -m socket -j PSW2_DIVERT"
|
||||
|
||||
$ip6t_m -N PSW2_OUTPUT
|
||||
$ip6t_m -A PSW2_OUTPUT -m mark --mark 0xff -j RETURN
|
||||
$ip6t_m -A PSW2_OUTPUT $(dst $IPSET_LANLIST6) -j RETURN
|
||||
$ip6t_m -A PSW2_OUTPUT $(dst $IPSET_VPSLIST6) -j RETURN
|
||||
$ip6t_m -A PSW2_OUTPUT $(dst $IPSET_WHITELIST6) ! -d $FAKE_IP_6 -j RETURN
|
||||
|
||||
ip -6 rule add fwmark 1 table 100
|
||||
ip -6 route add local ::/0 dev lo table 100
|
||||
|
||||
# 过滤Socks节点
|
||||
[ "$SOCKS_ENABLED" = "1" ] && {
|
||||
local ids=$(uci show $CONFIG | grep "=socks" | awk -F '.' '{print $2}' | awk -F '=' '{print $1}')
|
||||
#echolog "分析 Socks 服务所使用节点..."
|
||||
local id enabled node port msg num
|
||||
for id in $ids; do
|
||||
enabled=$(config_n_get $id enabled 0)
|
||||
[ "$enabled" == "1" ] || continue
|
||||
node=$(config_n_get $id node nil)
|
||||
port=$(config_n_get $id port 0)
|
||||
msg="Socks 服务 [:${port}]"
|
||||
if [ "$node" == "nil" ] || [ "$port" == "0" ]; then
|
||||
msg="${msg} 未配置完全,略过"
|
||||
else
|
||||
filter_node $node TCP > /dev/null 2>&1 &
|
||||
filter_node $node UDP > /dev/null 2>&1 &
|
||||
fi
|
||||
#echolog " - ${msg}"
|
||||
done
|
||||
}
|
||||
|
||||
[ "$ENABLED_DEFAULT_ACL" == 1 ] && {
|
||||
# 加载路由器自身代理 TCP
|
||||
if [ "$NODE" != "nil" ] && [ "$LOCALHOST_PROXY" = "1" ]; then
|
||||
echolog "加载路由器自身 TCP 代理..."
|
||||
|
||||
[ "$accept_icmp" = "1" ] && {
|
||||
$ipt_n -A OUTPUT -p icmp -j PSW2_OUTPUT
|
||||
$ipt_n -A PSW2_OUTPUT -p icmp -d $FAKE_IP $(REDIRECT)
|
||||
$ipt_n -A PSW2_OUTPUT -p icmp $(REDIRECT)
|
||||
}
|
||||
|
||||
[ "$accept_icmpv6" = "1" ] && {
|
||||
$ip6t_n -A OUTPUT -p ipv6-icmp -j PSW2_OUTPUT
|
||||
$ip6t_n -A PSW2_OUTPUT -p ipv6-icmp -d $FAKE_IP_6 $(REDIRECT)
|
||||
$ip6t_n -A PSW2_OUTPUT -p ipv6-icmp $(REDIRECT)
|
||||
}
|
||||
|
||||
local ipt_tmp=$ipt_n
|
||||
[ -n "${is_tproxy}" ] && {
|
||||
echolog " - 启用 TPROXY 模式"
|
||||
ipt_tmp=$ipt_m
|
||||
}
|
||||
|
||||
[ "$TCP_NO_REDIR_PORTS" != "disable" ] && {
|
||||
$ipt_tmp -A PSW2_OUTPUT -p tcp -m multiport --dport $TCP_NO_REDIR_PORTS -j RETURN
|
||||
$ip6t_m -A PSW2_OUTPUT -p tcp -m multiport --dport $TCP_NO_REDIR_PORTS -j RETURN
|
||||
echolog " - [$?]不代理TCP 端口:$TCP_NO_REDIR_PORTS"
|
||||
}
|
||||
|
||||
if [ "${ipt_tmp}" = "${ipt_n}" ]; then
|
||||
$ipt_n -A PSW2_OUTPUT -p tcp -d $FAKE_IP $(REDIRECT $REDIR_PORT)
|
||||
$ipt_n -A PSW2_OUTPUT -p tcp $(factor $TCP_REDIR_PORTS "-m multiport --dport") $(REDIRECT $REDIR_PORT)
|
||||
$ipt_n -A OUTPUT -p tcp -j PSW2_OUTPUT
|
||||
else
|
||||
$ipt_m -A PSW2_OUTPUT -p tcp -d $FAKE_IP -j PSW2_RULE
|
||||
$ipt_m -A PSW2_OUTPUT -p tcp $(factor $TCP_REDIR_PORTS "-m multiport --dport") -j PSW2_RULE
|
||||
$ipt_m -A PSW2 $(comment "本机") -p tcp -i lo $(REDIRECT $REDIR_PORT TPROXY)
|
||||
$ipt_m -A PSW2 $(comment "本机") -p tcp -i lo -j RETURN
|
||||
$ipt_m -A OUTPUT -p tcp -j PSW2_OUTPUT
|
||||
fi
|
||||
|
||||
if [ "$PROXY_IPV6" == "1" ]; then
|
||||
$ip6t_m -A PSW2_OUTPUT -p tcp -d $FAKE_IP_6 -j PSW2_RULE
|
||||
$ip6t_m -A PSW2_OUTPUT -p tcp $(factor $TCP_REDIR_PORTS "-m multiport --dport") -j PSW2_RULE
|
||||
$ip6t_m -A PSW2 $(comment "本机") -p tcp -i lo $(REDIRECT $REDIR_PORT TPROXY)
|
||||
$ip6t_m -A PSW2 $(comment "本机") -p tcp -i lo -j RETURN
|
||||
$ip6t_m -A OUTPUT -p tcp -j PSW2_OUTPUT
|
||||
fi
|
||||
|
||||
for iface in $IFACES; do
|
||||
$ipt_n -I PSW2_OUTPUT -o $iface -p tcp -j RETURN
|
||||
$ipt_m -I PSW2_OUTPUT -o $iface -p tcp -j RETURN
|
||||
done
|
||||
fi
|
||||
|
||||
# 处理轮换节点的分流或套娃
|
||||
filter_node $NODE TCP > /dev/null 2>&1 &
|
||||
filter_node $NODE UDP > /dev/null 2>&1 &
|
||||
|
||||
# 加载路由器自身代理 UDP
|
||||
if [ "$NODE" != "nil" ] && [ "$LOCALHOST_PROXY" = "1" ]; then
|
||||
echolog "加载路由器自身 UDP 代理..."
|
||||
|
||||
[ "$UDP_NO_REDIR_PORTS" != "disable" ] && {
|
||||
$ipt_m -A PSW2_OUTPUT -p udp -m multiport --dport $UDP_NO_REDIR_PORTS -j RETURN
|
||||
$ip6t_m -A PSW2_OUTPUT -p udp -m multiport --dport $UDP_NO_REDIR_PORTS -j RETURN
|
||||
echolog " - [$?]不代理 UDP 端口:$UDP_NO_REDIR_PORTS"
|
||||
}
|
||||
|
||||
$ipt_m -A PSW2_OUTPUT -p udp -d $FAKE_IP -j PSW2_RULE
|
||||
$ipt_m -A PSW2_OUTPUT -p udp $(factor $UDP_REDIR_PORTS "-m multiport --dport") -j PSW2_RULE
|
||||
$ipt_m -A PSW2 $(comment "本机") -p udp -i lo $(REDIRECT $REDIR_PORT TPROXY)
|
||||
$ipt_m -A PSW2 $(comment "本机") -p udp -i lo -j RETURN
|
||||
$ipt_m -A OUTPUT -p udp -j PSW2_OUTPUT
|
||||
|
||||
if [ "$PROXY_IPV6_UDP" == "1" ]; then
|
||||
$ip6t_m -A PSW2_OUTPUT -p udp -d $FAKE_IP_6 -j PSW2_RULE
|
||||
$ip6t_m -A PSW2_OUTPUT -p udp $(factor $UDP_REDIR_PORTS "-m multiport --dport") -j PSW2_RULE
|
||||
$ip6t_m -A PSW2 $(comment "本机") -p udp -i lo $(REDIRECT $REDIR_PORT TPROXY)
|
||||
$ip6t_m -A PSW2 $(comment "本机") -p udp -i lo -j RETURN
|
||||
$ip6t_m -A OUTPUT -p udp -j PSW2_OUTPUT
|
||||
fi
|
||||
|
||||
for iface in $IFACES; do
|
||||
$ipt_n -I PSW2_OUTPUT -o $iface -p udp -j RETURN
|
||||
$ipt_m -I PSW2_OUTPUT -o $iface -p udp -j RETURN
|
||||
done
|
||||
fi
|
||||
|
||||
$ipt_m -A PSW2 -p udp --dport 53 -j RETURN
|
||||
$ip6t_m -A PSW2 -p udp --dport 53 -j RETURN
|
||||
}
|
||||
|
||||
# 加载ACLS
|
||||
load_acl
|
||||
|
||||
echolog "防火墙规则加载完成!"
|
||||
}
|
||||
|
||||
del_firewall_rule() {
|
||||
for ipt in "$ipt_n" "$ipt_m" "$ip6t_n" "$ip6t_m"; do
|
||||
for chain in "PREROUTING" "OUTPUT"; do
|
||||
for i in $(seq 1 $($ipt -nL $chain | grep -c PSW2)); do
|
||||
local index=$($ipt --line-number -nL $chain | grep PSW2 | head -1 | awk '{print $1}')
|
||||
$ipt -D $chain $index 2>/dev/null
|
||||
done
|
||||
done
|
||||
for chain in "PSW2" "PSW2_OUTPUT" "PSW2_DIVERT" "PSW2_REDIRECT" "PSW2_RULE"; do
|
||||
$ipt -F $chain 2>/dev/null
|
||||
$ipt -X $chain 2>/dev/null
|
||||
done
|
||||
done
|
||||
|
||||
ip rule del fwmark 1 lookup 100 2>/dev/null
|
||||
ip route del local 0.0.0.0/0 dev lo table 100 2>/dev/null
|
||||
|
||||
ip -6 rule del fwmark 1 table 100 2>/dev/null
|
||||
ip -6 route del local ::/0 dev lo table 100 2>/dev/null
|
||||
|
||||
$DIR/app.sh echolog "删除相关防火墙规则完成。"
|
||||
}
|
||||
|
||||
flush_ipset() {
|
||||
del_firewall_rule
|
||||
for _name in $(ipset list | grep "Name: " | grep "passwall2_" | awk '{print $2}'); do
|
||||
destroy_ipset ${_name}
|
||||
done
|
||||
/etc/init.d/passwall2 reload
|
||||
}
|
||||
|
||||
flush_include() {
|
||||
echo '#!/bin/sh' >$FWI
|
||||
}
|
||||
|
||||
gen_include() {
|
||||
flush_include
|
||||
extract_rules() {
|
||||
local _ipt="${ipt}"
|
||||
[ "$1" == "6" ] && _ipt="${ip6t}"
|
||||
[ -z "${_ipt}" ] && return
|
||||
|
||||
echo "*$2"
|
||||
${_ipt}-save -t $2 | grep "PSW2" | grep -v "\-j PSW2$" | grep -v "socket \-j PSW2_DIVERT$" | sed -e "s/^-A \(OUTPUT\|PREROUTING\)/-I \1 1/"
|
||||
echo 'COMMIT'
|
||||
}
|
||||
local __ipt=""
|
||||
[ -n "${ipt}" ] && {
|
||||
__ipt=$(cat <<- EOF
|
||||
$ipt-save -c | grep -v "PSW2" | $ipt-restore -c
|
||||
$ipt-restore -n <<-EOT
|
||||
$(extract_rules 4 nat)
|
||||
$(extract_rules 4 mangle)
|
||||
EOT
|
||||
|
||||
[ "$accept_icmp" = "1" ] && \$(${MY_PATH} insert_rule_after "$ipt_n" "PREROUTING" "prerouting_rule" "-p icmp -j PSW2")
|
||||
[ -z "${is_tproxy}" ] && \$(${MY_PATH} insert_rule_after "$ipt_n" "PREROUTING" "prerouting_rule" "-p tcp -j PSW2")
|
||||
|
||||
\$(${MY_PATH} insert_rule_before "$ipt_m" "PREROUTING" "mwan3" "-j PSW2")
|
||||
\$(${MY_PATH} insert_rule_before "$ipt_m" "PREROUTING" "PSW2" "-p tcp -m socket -j PSW2_DIVERT")
|
||||
|
||||
WAN_IP=\$(${MY_PATH} get_wan_ip)
|
||||
|
||||
PR_INDEX=\$(${MY_PATH} RULE_LAST_INDEX "$ipt_n" PSW2 WAN_IP_RETURN -1)
|
||||
if [ \$PR_INDEX -ge 0 ]; then
|
||||
[ ! -z "\${WAN_IP}" ] && $ipt_n -R PSW2 \$PR_INDEX $(comment "WAN_IP_RETURN") -d "\${WAN_IP}" -j RETURN
|
||||
fi
|
||||
|
||||
PR_INDEX=\$(${MY_PATH} RULE_LAST_INDEX "$ipt_m" PSW2 WAN_IP_RETURN -1)
|
||||
if [ \$PR_INDEX -ge 0 ]; then
|
||||
[ ! -z "\${WAN_IP}" ] && $ipt_m -R PSW2 \$PR_INDEX $(comment "WAN_IP_RETURN") -d "\${WAN_IP}" -j RETURN
|
||||
fi
|
||||
EOF
|
||||
)
|
||||
}
|
||||
local __ip6t=""
|
||||
[ -n "${ip6t}" ] && {
|
||||
__ip6t=$(cat <<- EOF
|
||||
$ip6t-save -c | grep -v "PSW2" | $ip6t-restore -c
|
||||
$ip6t-restore -n <<-EOT
|
||||
$(extract_rules 6 nat)
|
||||
$(extract_rules 6 mangle)
|
||||
EOT
|
||||
|
||||
[ "$accept_icmpv6" = "1" ] && $ip6t_n -A PREROUTING -p ipv6-icmp -j PSW2
|
||||
|
||||
\$(${MY_PATH} insert_rule_before "$ip6t_m" "PREROUTING" "mwan3" "-j PSW2")
|
||||
\$(${MY_PATH} insert_rule_before "$ip6t_m" "PREROUTING" "PSW2" "-p tcp -m socket -j PSW2_DIVERT")
|
||||
|
||||
PR_INDEX=\$(${MY_PATH} RULE_LAST_INDEX "$ip6t_m" PSW2 WAN6_IP_RETURN -1)
|
||||
if [ \$PR_INDEX -ge 0 ]; then
|
||||
WAN6_IP=\$(${MY_PATH} get_wan6_ip)
|
||||
[ ! -z "\${WAN6_IP}" ] && $ip6t_m -R PSW2 \$PR_INDEX $(comment "WAN6_IP_RETURN") -d "\${WAN6_IP}" -j RETURN
|
||||
fi
|
||||
EOF
|
||||
)
|
||||
}
|
||||
cat <<-EOF >> $FWI
|
||||
${__ipt}
|
||||
|
||||
${__ip6t}
|
||||
EOF
|
||||
return 0
|
||||
}
|
||||
|
||||
get_ipt_bin() {
|
||||
echo $ipt
|
||||
}
|
||||
|
||||
get_ip6t_bin() {
|
||||
echo $ip6t
|
||||
}
|
||||
|
||||
start() {
|
||||
[ "$ENABLED_DEFAULT_ACL" == 0 -a "$ENABLED_ACLS" == 0 ] && return
|
||||
add_firewall_rule
|
||||
gen_include
|
||||
}
|
||||
|
||||
stop() {
|
||||
del_firewall_rule
|
||||
flush_include
|
||||
}
|
||||
|
||||
arg1=$1
|
||||
shift
|
||||
case $arg1 in
|
||||
RULE_LAST_INDEX)
|
||||
RULE_LAST_INDEX "$@"
|
||||
;;
|
||||
insert_rule_before)
|
||||
insert_rule_before "$@"
|
||||
;;
|
||||
insert_rule_after)
|
||||
insert_rule_after "$@"
|
||||
;;
|
||||
flush_ipset)
|
||||
flush_ipset
|
||||
;;
|
||||
get_ipt_bin)
|
||||
get_ipt_bin
|
||||
;;
|
||||
get_ip6t_bin)
|
||||
get_ip6t_bin
|
||||
;;
|
||||
get_wan_ip)
|
||||
get_wan_ip
|
||||
;;
|
||||
get_wan6_ip)
|
||||
get_wan6_ip
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
start)
|
||||
start
|
||||
;;
|
||||
*) ;;
|
||||
esac
|
47
luci-app-passwall2/root/usr/share/passwall2/monitor.sh
Executable file
47
luci-app-passwall2/root/usr/share/passwall2/monitor.sh
Executable file
@ -0,0 +1,47 @@
|
||||
#!/bin/sh
|
||||
|
||||
CONFIG=passwall2
|
||||
TMP_PATH=/tmp/etc/$CONFIG
|
||||
TMP_SCRIPT_FUNC_PATH=$TMP_PATH/script_func
|
||||
LOCK_FILE_DIR=/tmp/lock
|
||||
LOCK_FILE=${LOCK_FILE_DIR}/${CONFIG}_script.lock
|
||||
|
||||
config_n_get() {
|
||||
local ret=$(uci -q get $CONFIG.$1.$2 2>/dev/null)
|
||||
echo ${ret:=$3}
|
||||
}
|
||||
|
||||
config_t_get() {
|
||||
local index=0
|
||||
[ -n "$4" ] && index=$4
|
||||
local ret=$(uci -q get $CONFIG.@$1[$index].$2 2>/dev/null)
|
||||
echo ${ret:=$3}
|
||||
}
|
||||
|
||||
ENABLED=$(config_t_get global enabled 0)
|
||||
[ "$ENABLED" != 1 ] && return 1
|
||||
ENABLED=$(config_t_get global_delay start_daemon 0)
|
||||
[ "$ENABLED" != 1 ] && return 1
|
||||
sleep 58s
|
||||
while [ "$ENABLED" -eq 1 ]; do
|
||||
[ -f "$LOCK_FILE" ] && {
|
||||
sleep 6s
|
||||
continue
|
||||
}
|
||||
touch $LOCK_FILE
|
||||
[ -d ${TMP_SCRIPT_FUNC_PATH} ] && {
|
||||
for filename in $(ls ${TMP_SCRIPT_FUNC_PATH} | grep -v "^_"); do
|
||||
cmd=$(cat ${TMP_SCRIPT_FUNC_PATH}/${filename})
|
||||
cmd_check=$(echo $cmd | awk -F '>' '{print $1}')
|
||||
[ -n "$(echo $cmd_check | grep "dns2socks")" ] && cmd_check=$(echo $cmd_check | sed "s#:# #g")
|
||||
icount=$(pgrep -f "$(echo $cmd_check)" | wc -l)
|
||||
if [ $icount = 0 ]; then
|
||||
#echo "${cmd} 进程挂掉,重启" >> /tmp/log/passwall2.log
|
||||
eval $(echo "nohup ${cmd} 2>&1 &") >/dev/null 2>&1 &
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
rm -f $LOCK_FILE
|
||||
sleep 58s
|
||||
done
|
984
luci-app-passwall2/root/usr/share/passwall2/nftables.sh
Executable file
984
luci-app-passwall2/root/usr/share/passwall2/nftables.sh
Executable file
@ -0,0 +1,984 @@
|
||||
#!/bin/bash
|
||||
|
||||
DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
MY_PATH=$DIR/nftables.sh
|
||||
NFTSET_LANLIST="passwall2_lanlist"
|
||||
NFTSET_VPSLIST="passwall2_vpslist"
|
||||
NFTSET_WHITELIST="passwall2_whitelist"
|
||||
|
||||
NFTSET_LANLIST6="passwall2_lanlist6"
|
||||
NFTSET_VPSLIST6="passwall2_vpslist6"
|
||||
NFTSET_WHITELIST6="passwall2_whitelist6"
|
||||
|
||||
FORCE_INDEX=0
|
||||
|
||||
. /lib/functions/network.sh
|
||||
|
||||
FWI=$(uci -q get firewall.passwall2.path 2>/dev/null)
|
||||
FAKE_IP="198.18.0.0/16"
|
||||
FAKE_IP_6="fc00::/18"
|
||||
|
||||
factor() {
|
||||
if [ -z "$1" ] || [ -z "$2" ]; then
|
||||
echo ""
|
||||
elif [ "$1" == "1:65535" ]; then
|
||||
echo ""
|
||||
# acl mac address
|
||||
elif [ -n "$(echo $1 | grep -E '([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}')" ]; then
|
||||
echo "$2 {$1}"
|
||||
else
|
||||
echo "$2 {$(echo $1 | sed 's/:/-/g')}"
|
||||
fi
|
||||
}
|
||||
|
||||
insert_rule_before() {
|
||||
[ $# -ge 4 ] || {
|
||||
return 1
|
||||
}
|
||||
local table_name="${1}"; shift
|
||||
local chain_name="${1}"; shift
|
||||
local keyword="${1}"; shift
|
||||
local rule="${1}"; shift
|
||||
local default_index="${1}"; shift
|
||||
default_index=${default_index:-0}
|
||||
local _index=$(nft -a list chain $table_name $chain_name 2>/dev/null | grep "$keyword" | awk -F '# handle ' '{print$2}' | head -n 1 | awk '{print $1}')
|
||||
if [ -z "${_index}" ] && [ "${default_index}" = "0" ]; then
|
||||
nft "add rule $table_name $chain_name $rule"
|
||||
else
|
||||
if [ -z "${_index}" ]; then
|
||||
_index=${default_index}
|
||||
fi
|
||||
nft "insert rule $table_name $chain_name position $_index $rule"
|
||||
fi
|
||||
}
|
||||
|
||||
insert_rule_after() {
|
||||
[ $# -ge 4 ] || {
|
||||
return 1
|
||||
}
|
||||
local table_name="${1}"; shift
|
||||
local chain_name="${1}"; shift
|
||||
local keyword="${1}"; shift
|
||||
local rule="${1}"; shift
|
||||
local default_index="${1}"; shift
|
||||
default_index=${default_index:-0}
|
||||
local _index=$(nft -a list chain $table_name $chain_name 2>/dev/null | grep "$keyword" | awk -F '# handle ' '{print$2}' | head -n 1 | awk '{print $1}')
|
||||
if [ -z "${_index}" ] && [ "${default_index}" = "0" ]; then
|
||||
nft "add rule $table_name $chain_name $rule"
|
||||
else
|
||||
if [ -n "${_index}" ]; then
|
||||
_index=$((_index + 1))
|
||||
else
|
||||
_index=${default_index}
|
||||
fi
|
||||
nft "insert rule $table_name $chain_name position $_index $rule"
|
||||
fi
|
||||
}
|
||||
|
||||
RULE_LAST_INDEX() {
|
||||
[ $# -ge 3 ] || {
|
||||
echolog "索引列举方式不正确(nftables),终止执行!"
|
||||
return 1
|
||||
}
|
||||
local table_name="${1}"; shift
|
||||
local chain_name="${1}"; shift
|
||||
local keyword="${1}"; shift
|
||||
local default="${1:-0}"; shift
|
||||
local _index=$(nft -a list chain $table_name $chain_name 2>/dev/null | grep "$keyword" | awk -F '# handle ' '{print$2}' | head -n 1 | awk '{print $1}')
|
||||
echo "${_index:-${default}}"
|
||||
}
|
||||
|
||||
REDIRECT() {
|
||||
local s="counter redirect"
|
||||
[ -n "$1" ] && {
|
||||
local s="$s to :$1"
|
||||
[ "$2" == "MARK" ] && s="counter meta mark set $1"
|
||||
[ "$2" == "TPROXY" ] && {
|
||||
s="counter meta mark 1 tproxy to :$1"
|
||||
}
|
||||
[ "$2" == "TPROXY4" ] && {
|
||||
s="counter meta mark 1 tproxy ip to :$1"
|
||||
}
|
||||
[ "$2" == "TPROXY6" ] && {
|
||||
s="counter meta mark 1 tproxy ip6 to :$1"
|
||||
}
|
||||
|
||||
}
|
||||
echo $s
|
||||
}
|
||||
|
||||
destroy_nftset() {
|
||||
for i in "$@"; do
|
||||
nft flush set inet fw4 $i 2>/dev/null
|
||||
nft delete set inet fw4 $i 2>/dev/null
|
||||
done
|
||||
}
|
||||
|
||||
insert_nftset() {
|
||||
local nftset_name="${1}"; shift
|
||||
local nftset_elements
|
||||
|
||||
nftset_elements=$(echo -e $@ | sed 's/\s/, /g')
|
||||
[ -n "${nftset_elements}" ] && {
|
||||
mkdir -p $TMP_PATH2/nftset
|
||||
|
||||
cat > "$TMP_PATH2/nftset/$nftset_name" <<-EOF
|
||||
define $nftset_name = {$nftset_elements}
|
||||
add element inet fw4 $nftset_name \$$nftset_name
|
||||
EOF
|
||||
nft -f "$TMP_PATH2/nftset/$nftset_name"
|
||||
rm -rf "$TMP_PATH2/nftset"
|
||||
}
|
||||
}
|
||||
|
||||
gen_nftset() {
|
||||
local nftset_name="${1}"; shift
|
||||
local ip_type="${1}"; shift
|
||||
|
||||
nft "list set inet fw4 $nftset_name" &>/dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
nft "add set inet fw4 $nftset_name { type $ip_type; flags interval; auto-merge; }"
|
||||
fi
|
||||
[ -n "${1}" ] && insert_nftset $nftset_name $@
|
||||
}
|
||||
|
||||
get_action_chain_name() {
|
||||
echo "全局代理"
|
||||
}
|
||||
|
||||
gen_lanlist() {
|
||||
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.168.0.0/16
|
||||
224.0.0.0/4
|
||||
240.0.0.0/4
|
||||
EOF
|
||||
}
|
||||
|
||||
gen_lanlist_6() {
|
||||
cat <<-EOF
|
||||
::/128
|
||||
::1/128
|
||||
::ffff:0:0/96
|
||||
::ffff:0:0:0/96
|
||||
64:ff9b::/96
|
||||
100::/64
|
||||
2001::/32
|
||||
2001:20::/28
|
||||
2001:db8::/32
|
||||
2002::/16
|
||||
fc00::/7
|
||||
fe80::/10
|
||||
ff00::/8
|
||||
EOF
|
||||
}
|
||||
|
||||
get_wan_ip() {
|
||||
local NET_IF
|
||||
local NET_ADDR
|
||||
|
||||
network_flush_cache
|
||||
network_find_wan NET_IF
|
||||
network_get_ipaddr NET_ADDR "${NET_IF}"
|
||||
|
||||
echo $NET_ADDR
|
||||
}
|
||||
|
||||
get_wan6_ip() {
|
||||
local NET_IF
|
||||
local NET_ADDR
|
||||
|
||||
network_flush_cache
|
||||
network_find_wan6 NET_IF
|
||||
network_get_ipaddr6 NET_ADDR "${NET_IF}"
|
||||
|
||||
echo $NET_ADDR
|
||||
}
|
||||
|
||||
load_acl() {
|
||||
[ "$ENABLED_ACLS" == 1 ] && {
|
||||
acl_app
|
||||
echolog "访问控制:"
|
||||
for sid in $(ls -F ${TMP_ACL_PATH} | grep '/$' | awk -F '/' '{print $1}'); do
|
||||
eval $(uci -q show "${CONFIG}.${sid}" | cut -d'.' -sf 3-)
|
||||
|
||||
tcp_no_redir_ports=${tcp_no_redir_ports:-default}
|
||||
udp_no_redir_ports=${udp_no_redir_ports:-default}
|
||||
tcp_proxy_mode="global"
|
||||
udp_proxy_mode="global"
|
||||
node=${node:-default}
|
||||
[ "$tcp_no_redir_ports" = "default" ] && tcp_no_redir_ports=$TCP_NO_REDIR_PORTS
|
||||
[ "$udp_no_redir_ports" = "default" ] && udp_no_redir_ports=$UDP_NO_REDIR_PORTS
|
||||
[ "$tcp_redir_ports" = "default" ] && tcp_redir_ports=$TCP_REDIR_PORTS
|
||||
[ "$udp_redir_ports" = "default" ] && udp_redir_ports=$UDP_REDIR_PORTS
|
||||
|
||||
node_remark=$(config_n_get $NODE remarks)
|
||||
[ -s "${TMP_ACL_PATH}/${sid}/var_node" ] && node=$(cat ${TMP_ACL_PATH}/${sid}/var_node)
|
||||
[ -s "${TMP_ACL_PATH}/${sid}/var_port" ] && redir_port=$(cat ${TMP_ACL_PATH}/${sid}/var_port)
|
||||
[ -n "$node" ] && [ "$node" != "default" ] && node_remark=$(config_n_get $node remarks)
|
||||
|
||||
for i in $(cat ${TMP_ACL_PATH}/${sid}/rule_list); do
|
||||
if [ -n "$(echo ${i} | grep '^iprange:')" ]; then
|
||||
_iprange=$(echo ${i} | sed 's#iprange:##g')
|
||||
_ipt_source=$(factor ${_iprange} "ip saddr")
|
||||
msg="备注【$remarks】,IP range【${_iprange}】,"
|
||||
elif [ -n "$(echo ${i} | grep '^ipset:')" ]; then
|
||||
_ipset=$(echo ${i} | sed 's#ipset:##g')
|
||||
_ipt_source="ip daddr @${_ipset}"
|
||||
msg="备注【$remarks】,NFTset【${_ipset}】,"
|
||||
elif [ -n "$(echo ${i} | grep '^ip:')" ]; then
|
||||
_ip=$(echo ${i} | sed 's#ip:##g')
|
||||
_ipt_source=$(factor ${_ip} "ip saddr")
|
||||
msg="备注【$remarks】,IP【${_ip}】,"
|
||||
elif [ -n "$(echo ${i} | grep '^mac:')" ]; then
|
||||
_mac=$(echo ${i} | sed 's#mac:##g')
|
||||
_ipt_source=$(factor ${_mac} "ether saddr")
|
||||
msg="备注【$remarks】,MAC【${_mac}】,"
|
||||
else
|
||||
continue
|
||||
fi
|
||||
|
||||
[ -n "$redir_port" ] && {
|
||||
if [ "$tcp_proxy_mode" != "disable" ]; then
|
||||
[ -s "${TMP_ACL_PATH}/${sid}/var_redirect_dns_port" ] && nft "add rule inet fw4 PSW2_REDIRECT ip protocol udp ${_ipt_source} udp dport 53 counter redirect to $(cat ${TMP_ACL_PATH}/${sid}/var_redirect_dns_port) comment \"$remarks\""
|
||||
msg2="${msg}使用TCP节点[$node_remark] [$(get_action_chain_name $tcp_proxy_mode)]"
|
||||
if [ -n "${is_tproxy}" ]; then
|
||||
msg2="${msg2}(TPROXY:${redir_port})代理"
|
||||
else
|
||||
msg2="${msg2}(REDIRECT:${redir_port})代理"
|
||||
fi
|
||||
|
||||
[ "$accept_icmp" = "1" ] && {
|
||||
nft "add rule inet fw4 PSW2_ICMP_REDIRECT ip protocol icmp ${_ipt_source} ip daddr $FAKE_IP $(REDIRECT) comment \"$remarks\""
|
||||
nft "add rule inet fw4 PSW2_ICMP_REDIRECT ip protocol icmp ${_ipt_source} $(REDIRECT) comment \"$remarks\""
|
||||
}
|
||||
|
||||
[ "$accept_icmpv6" = "1" ] && [ "$PROXY_IPV6" == "1" ] && {
|
||||
nft "add rule inet fw4 PSW2_ICMP_REDIRECT meta l4proto icmpv6 ${_ipt_source} ip6 daddr $FAKE_IP_6 $(REDIRECT) comment \"$remarks\"" 2>/dev/null
|
||||
nft "add rule inet fw4 PSW2_ICMP_REDIRECT meta l4proto icmpv6 ${_ipt_source} $(REDIRECT) comment \"$remarks\"" 2>/dev/null
|
||||
}
|
||||
|
||||
[ "$tcp_no_redir_ports" != "disable" ] && {
|
||||
nft "add rule inet fw4 $nft_prerouting_chain ${_ipt_source} ip protocol tcp tcp dport {$tcp_no_redir_ports} counter return comment \"$remarks\""
|
||||
nft "add rule inet fw4 PSW2_MANGLE_V6 comment ${_ipt_source} meta l4proto tcp tcp dport {$tcp_no_redir_ports} counter return comment \"$remarks\""
|
||||
msg2="${msg2}[$?]除${tcp_no_redir_ports}外的"
|
||||
}
|
||||
msg2="${msg2}所有端口"
|
||||
|
||||
if [ -z "${is_tproxy}" ]; then
|
||||
nft "add rule inet fw4 PSW2_NAT ${_ipt_source} ip daddr $FAKE_IP $(REDIRECT $redir_port) comment \"$remarks\""
|
||||
nft "add rule inet fw4 PSW2_NAT ${_ipt_source} $(factor $tcp_redir_ports "tcp dport") $(REDIRECT $redir_port) comment \"$remarks\""
|
||||
else
|
||||
nft "add rule inet fw4 PSW2_MANGLE ip protocol tcp ${_ipt_source} ip daddr $FAKE_IP counter jump PSW2_RULE comment \"$remarks\""
|
||||
nft "add rule inet fw4 PSW2_MANGLE ip protocol tcp ${_ipt_source} $(factor $tcp_redir_ports "tcp dport") counter jump PSW2_RULE comment \"$remarks\""
|
||||
nft "add rule inet fw4 PSW2_MANGLE meta nfproto {ipv4} meta l4proto tcp ${_ipt_source} $(REDIRECT $redir_port TPROXY4) comment \"$remarks\""
|
||||
fi
|
||||
|
||||
[ "$PROXY_IPV6" == "1" ] && {
|
||||
nft "add rule inet fw4 PSW2_MANGLE_V6 meta l4proto tcp ${_ipt_source} ip6 daddr $FAKE_IP_6 counter jump PSW2_RULE comment \"$remarks\""
|
||||
nft "add rule inet fw4 PSW2_MANGLE_V6 meta l4proto tcp ${_ipt_source} $(factor $tcp_redir_ports "tcp dport") jump PSW2_RULE comment \"$remarks\"" 2>/dev/null
|
||||
nft "add rule inet fw4 PSW2_MANGLE_V6 meta l4proto tcp ${_ipt_source} $(REDIRECT $redir_port TPROXY) comment \"$remarks\"" 2>/dev/null
|
||||
}
|
||||
else
|
||||
msg2="${msg}不代理TCP"
|
||||
fi
|
||||
echolog " - ${msg2}"
|
||||
}
|
||||
|
||||
nft "add rule inet fw4 $nft_prerouting_chain ip protocol tcp ${_ipt_source} counter return comment \"$remarks\""
|
||||
nft "add rule inet fw4 PSW2_MANGLE_V6 meta l4proto tcp ${_ipt_source} counter return comment \"$remarks\"" 2>/dev/null
|
||||
|
||||
[ -n "$redir_port" ] && {
|
||||
if [ "$udp_proxy_mode" != "disable" ]; then
|
||||
msg2="${msg}使用UDP节点[$node_remark] [$(get_action_chain_name $udp_proxy_mode)]"
|
||||
msg2="${msg2}(TPROXY:${redir_port})代理"
|
||||
[ "$udp_no_redir_ports" != "disable" ] && {
|
||||
nft add rule inet fw4 PSW2_MANGLE meta l4proto udp ${_ipt_source} $(factor $udp_no_redir_ports "udp dport") counter return
|
||||
nft add rule inet fw4 PSW2_MANGLE_V6 meta l4proto udp ${_ipt_source} $(factor $udp_no_redir_ports "udp dport") counter return 2>/dev/null
|
||||
msg2="${msg2}[$?]除${udp_no_redir_ports}外的"
|
||||
}
|
||||
msg2="${msg2}所有端口"
|
||||
|
||||
nft "add rule inet fw4 PSW2_MANGLE ip protocol udp ${_ipt_source} ip daddr $FAKE_IP counter jump PSW2_RULE comment \"$remarks\""
|
||||
nft "add rule inet fw4 PSW2_MANGLE ip protocol udp ${_ipt_source} $(factor $udp_redir_ports "udp dport") jump PSW2_RULE comment \"$remarks\""
|
||||
nft "add rule inet fw4 PSW2_MANGLE ip protocol udp ${_ipt_source} $(REDIRECT $redir_port TPROXY4) comment \"$remarks\""
|
||||
|
||||
[ "$PROXY_IPV6" == "1" ] && [ "$PROXY_IPV6_UDP" == "1" ] && {
|
||||
nft "add rule inet fw4 PSW2_MANGLE_V6 meta l4proto udp ${_ipt_source} ip6 daddr $FAKE_IP_6 counter jump PSW2_RULE comment \"$remarks\""
|
||||
nft "add rule inet fw4 PSW2_MANGLE_V6 meta l4proto udp ${_ipt_source} $(factor $udp_redir_ports "udp dport") counter jump PSW2_RULE comment \"$remarks\"" 2>/dev/null
|
||||
nft "add rule inet fw4 PSW2_MANGLE_V6 meta l4proto udp ${_ipt_source} $(REDIRECT $redir_port TPROXY) comment \"$remarks\"" 2>/dev/null
|
||||
}
|
||||
else
|
||||
msg2="${msg}不代理UDP"
|
||||
fi
|
||||
echolog " - ${msg2}"
|
||||
}
|
||||
nft "add rule inet fw4 PSW2_MANGLE ip protocol udp ${_ipt_source} counter return comment \"$remarks\""
|
||||
nft "add rule inet fw4 PSW2_MANGLE_V6 meta l4proto udp ${_ipt_source} counter return comment \"$remarks\"" 2>/dev/null
|
||||
done
|
||||
unset enabled sid remarks sources tcp_proxy_mode udp_proxy_mode tcp_no_redir_ports udp_no_redir_ports tcp_redir_ports udp_redir_ports node
|
||||
unset _ip _mac _iprange _ipset _ip_or_mac rule_list redir_port node_remark
|
||||
unset msg msg2
|
||||
done
|
||||
}
|
||||
|
||||
[ "$ENABLED_DEFAULT_ACL" == 1 ] && {
|
||||
# 加载默认代理模式
|
||||
if [ "$TCP_PROXY_MODE" != "disable" ]; then
|
||||
[ "$TCP_NO_REDIR_PORTS" != "disable" ] && {
|
||||
nft add rule inet fw4 $nft_prerouting_chain ip protocol tcp $(factor $TCP_NO_REDIR_PORTS "tcp dport") counter return comment \"默认\"
|
||||
nft add rule inet fw4 PSW2_MANGLE_V6 meta l4proto tcp $(factor $TCP_NO_REDIR_PORTS "tcp dport") counter return comment \"默认\"
|
||||
}
|
||||
[ "$NODE" != "nil" ] && {
|
||||
msg="TCP默认代理:使用TCP节点[$(config_n_get $NODE remarks)] [$(get_action_chain_name $TCP_PROXY_MODE)]"
|
||||
if [ -n "${is_tproxy}" ]; then
|
||||
msg="${msg}(TPROXY:${REDIR_PORT})代理"
|
||||
else
|
||||
msg="${msg}(REDIRECT:${REDIR_PORT})代理"
|
||||
fi
|
||||
|
||||
[ "$TCP_NO_REDIR_PORTS" != "disable" ] && msg="${msg}除${TCP_NO_REDIR_PORTS}外的"
|
||||
msg="${msg}所有端口"
|
||||
|
||||
[ "$accept_icmp" = "1" ] && {
|
||||
nft "add rule inet fw4 PSW2_ICMP_REDIRECT ip protocol icmp ip daddr $FAKE_IP $(REDIRECT) comment \"默认\""
|
||||
nft "add rule inet fw4 PSW2_ICMP_REDIRECT ip protocol icmp $(REDIRECT) comment \"默认\""
|
||||
}
|
||||
|
||||
[ "$accept_icmpv6" = "1" ] && [ "$PROXY_IPV6" == "1" ] && {
|
||||
nft "add rule inet fw4 PSW2_ICMP_REDIRECT meta l4proto icmpv6 ip6 daddr $FAKE_IP_6 $(REDIRECT) comment \"默认\""
|
||||
nft "add rule inet fw4 PSW2_ICMP_REDIRECT meta l4proto icmpv6 $(REDIRECT) comment \"默认\""
|
||||
}
|
||||
|
||||
if [ -z "${is_tproxy}" ]; then
|
||||
nft "add rule inet fw4 PSW2_NAT ip protocol tcp ip daddr $FAKE_IP $(REDIRECT $REDIR_PORT) comment \"默认\""
|
||||
nft "add rule inet fw4 PSW2_NAT ip protocol tcp $(factor $TCP_REDIR_PORTS "tcp dport") $(REDIRECT $REDIR_PORT) comment \"默认\""
|
||||
else
|
||||
nft "add rule inet fw4 PSW2_MANGLE ip protocol tcp ip daddr $FAKE_IP counter jump PSW2_RULE comment \"默认\""
|
||||
nft "add rule inet fw4 PSW2_MANGLE ip protocol tcp $(factor $TCP_REDIR_PORTS "tcp dport") jump PSW2_RULE comment \"默认\""
|
||||
nft "add rule inet fw4 PSW2_MANGLE meta l4proto tcp $(REDIRECT $REDIR_PORT TPROXY) comment \"默认\""
|
||||
fi
|
||||
|
||||
[ "$PROXY_IPV6" == "1" ] && {
|
||||
nft "add rule inet fw4 PSW2_MANGLE_V6 meta l4proto tcp ip6 daddr $FAKE_IP_6 jump PSW2_RULE comment \"默认\""
|
||||
nft "add rule inet fw4 PSW2_MANGLE_V6 meta l4proto tcp $(factor $TCP_REDIR_PORTS "tcp dport") jump PSW2_RULE comment \"默认\""
|
||||
nft "add rule inet fw4 PSW2_MANGLE_V6 meta l4proto tcp $(REDIRECT $REDIR_PORT TPROXY) comment \"默认\""
|
||||
}
|
||||
|
||||
echolog "${msg}"
|
||||
}
|
||||
fi
|
||||
|
||||
# 加载UDP默认代理模式
|
||||
if [ "$UDP_PROXY_MODE" != "disable" ]; then
|
||||
[ "$UDP_NO_REDIR_PORTS" != "disable" ] && {
|
||||
nft "add inet fw4 PSW2_MANGLE ip protocol udp $(factor $UDP_NO_REDIR_PORTS "udp dport") counter return comment \"默认\""
|
||||
nft "add inet fw4 PSW2_MANGLE_V6 counter meta l4proto udp $(factor $UDP_NO_REDIR_PORTS "udp dport") counter return comment \"默认\""
|
||||
}
|
||||
|
||||
[ -n "1" ] && {
|
||||
msg="UDP默认代理:使用UDP节点[$(config_n_get $NODE remarks)] [$(get_action_chain_name $UDP_PROXY_MODE)](TPROXY:${REDIR_PORT})代理"
|
||||
|
||||
[ "$UDP_NO_REDIR_PORTS" != "disable" ] && msg="${msg}除${UDP_NO_REDIR_PORTS}外的"
|
||||
msg="${msg}所有端口"
|
||||
|
||||
nft "add rule inet fw4 PSW2_MANGLE ip protocol udp ip daddr $FAKE_IP counter jump PSW2_RULE comment \"默认\""
|
||||
nft "add rule inet fw4 PSW2_MANGLE ip protocol udp $(factor $UDP_REDIR_PORTS "udp dport") jump PSW2_RULE comment \"默认\""
|
||||
nft "add rule inet fw4 PSW2_MANGLE meta l4proto udp $(REDIRECT $REDIR_PORT TPROXY) comment \"默认\""
|
||||
|
||||
[ "$PROXY_IPV6" == "1" ] && [ "$PROXY_IPV6_UDP" == "1" ] && {
|
||||
nft "add rule inet fw4 PSW2_MANGLE_V6 meta l4proto udp ip6 daddr $FAKE_IP_6 jump PSW2_RULE comment \"默认\""
|
||||
nft "add rule inet fw4 PSW2_MANGLE_V6 meta l4proto udp $(factor $UDP_REDIR_PORTS "udp dport") jump PSW2_RULE comment \"默认\""
|
||||
nft "add rule inet fw4 PSW2_MANGLE_V6 meta l4proto udp $(REDIRECT $REDIR_PORT TPROXY) comment \"默认\""
|
||||
}
|
||||
|
||||
echolog "${msg}"
|
||||
udp_flag=1
|
||||
}
|
||||
fi
|
||||
}
|
||||
}
|
||||
|
||||
filter_haproxy() {
|
||||
for item in $(uci show $CONFIG | grep ".lbss=" | cut -d "'" -f 2); do
|
||||
local ip=$(get_host_ip ipv4 $(echo $item | awk -F ":" '{print $1}') 1)
|
||||
[ -n "$ip" ] && insert_nftset $NFTSET_VPSLIST $ip
|
||||
done
|
||||
echolog "加入负载均衡的节点到nftset[$NFTSET_VPSLIST]直连完成"
|
||||
}
|
||||
|
||||
filter_vps_addr() {
|
||||
for server_host in $@; do
|
||||
local vps_ip4=$(get_host_ip "ipv4" ${server_host})
|
||||
local vps_ip6=$(get_host_ip "ipv6" ${server_host})
|
||||
[ -n "$vps_ip4" ] && insert_nftset $NFTSET_VPSLIST $vps_ip4
|
||||
[ -n "$vps_ip6" ] && insert_nftset $NFTSET_VPSLIST6 $vps_ip6
|
||||
done
|
||||
}
|
||||
|
||||
filter_vpsip() {
|
||||
insert_nftset $NFTSET_VPSLIST $(uci show $CONFIG | grep ".address=" | cut -d "'" -f 2 | grep -E "([0-9]{1,3}[\.]){3}[0-9]{1,3}" | sed -e "/^$/d")
|
||||
insert_nftset $NFTSET_VPSLIST6 $(uci show $CONFIG | grep ".address=" | cut -d "'" -f 2 | grep -E "([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4}" | sed -e "/^$/d")
|
||||
echolog "加入所有节点到nftset[$NFTSET_VPSLIST]直连完成"
|
||||
}
|
||||
|
||||
filter_node() {
|
||||
local proxy_node=${1}
|
||||
local stream=$(echo ${2} | tr 'A-Z' 'a-z')
|
||||
local proxy_port=${3}
|
||||
|
||||
filter_rules() {
|
||||
local node=${1}
|
||||
local stream=${2}
|
||||
local _proxy=${3}
|
||||
local _port=${4}
|
||||
local _is_tproxy msg msg2
|
||||
|
||||
if [ -n "$node" ] && [ "$node" != "nil" ]; then
|
||||
local type=$(echo $(config_n_get $node type) | tr 'A-Z' 'a-z')
|
||||
local address=$(config_n_get $node address)
|
||||
local port=$(config_n_get $node port)
|
||||
_is_tproxy=${is_tproxy}
|
||||
[ "$stream" == "udp" ] && _is_tproxy="TPROXY"
|
||||
if [ -n "${_is_tproxy}" ]; then
|
||||
msg="TPROXY"
|
||||
else
|
||||
msg="REDIRECT"
|
||||
fi
|
||||
else
|
||||
echolog " - 节点配置不正常,略过"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local ADD_INDEX=$FORCE_INDEX
|
||||
for _ipt in 4 6; do
|
||||
[ "$_ipt" == "4" ] && _ip_type=ip && _set_name=$NFTSET_VPSLIST
|
||||
[ "$_ipt" == "6" ] && _ip_type=ip6 && _set_name=$NFTSET_VPSLIST6
|
||||
nft "list chain inet fw4 $nft_output_chain" 2>/dev/null | grep -q "${address}:${port}"
|
||||
if [ $? -ne 0 ]; then
|
||||
unset dst_rule
|
||||
local dst_rule="jump PSW2_RULE"
|
||||
msg2="按规则路由(${msg})"
|
||||
[ -n "${is_tproxy}" ] || {
|
||||
dst_rule=$(REDIRECT $_port)
|
||||
msg2="套娃使用(${msg}:${port} -> ${_port})"
|
||||
}
|
||||
[ -n "$_proxy" ] && [ "$_proxy" == "1" ] && [ -n "$_port" ] || {
|
||||
ADD_INDEX=$(RULE_LAST_INDEX "inet fw4" $nft_output_chain $_set_name $FORCE_INDEX)
|
||||
dst_rule="return"
|
||||
msg2="直连代理"
|
||||
}
|
||||
nft "insert rule inet fw4 $nft_output_chain position $ADD_INDEX meta l4proto $stream $_ip_type daddr $address $stream dport $port $dst_rule comment \"${address}:${port}\"" 2>/dev/null
|
||||
else
|
||||
msg2="已配置过的节点,"
|
||||
fi
|
||||
done
|
||||
msg="[$?]$(echo ${2} | tr 'a-z' 'A-Z')${msg2}使用链${ADD_INDEX},节点(${type}):${address}:${port}"
|
||||
#echolog " - ${msg}"
|
||||
}
|
||||
|
||||
local proxy_protocol=$(config_n_get $proxy_node protocol)
|
||||
local proxy_type=$(echo $(config_n_get $proxy_node type nil) | tr 'A-Z' 'a-z')
|
||||
[ "$proxy_type" == "nil" ] && echolog " - 节点配置不正常,略过!:${proxy_node}" && return 0
|
||||
if [ "$proxy_protocol" == "_balancing" ]; then
|
||||
#echolog " - 多节点负载均衡(${proxy_type})..."
|
||||
proxy_node=$(config_n_get $proxy_node balancing_node)
|
||||
for _node in $proxy_node; do
|
||||
filter_rules "$_node" "$stream"
|
||||
done
|
||||
elif [ "$proxy_protocol" == "_shunt" ]; then
|
||||
#echolog " - 按请求目的地址分流(${proxy_type})..."
|
||||
local default_node=$(config_n_get $proxy_node default_node _direct)
|
||||
local main_node=$(config_n_get $proxy_node main_node nil)
|
||||
if [ "$main_node" != "nil" ]; then
|
||||
filter_rules $main_node $stream
|
||||
else
|
||||
if [ "$default_node" != "_direct" ] && [ "$default_node" != "_blackhole" ]; then
|
||||
filter_rules $default_node $stream
|
||||
fi
|
||||
fi
|
||||
:<<!
|
||||
local default_node_address=$(get_host_ip ipv4 $(config_n_get $default_node address) 1)
|
||||
local default_node_port=$(config_n_get $default_node port)
|
||||
|
||||
local shunt_ids=$(uci show $CONFIG | grep "=shunt_rules" | awk -F '.' '{print $2}' | awk -F '=' '{print $1}')
|
||||
for shunt_id in $shunt_ids; do
|
||||
#local shunt_proxy=$(config_n_get $proxy_node "${shunt_id}_proxy" 0)
|
||||
local shunt_proxy=0
|
||||
local shunt_node=$(config_n_get $proxy_node "${shunt_id}" nil)
|
||||
[ "$shunt_node" != "nil" ] && {
|
||||
[ "$shunt_proxy" == 1 ] && {
|
||||
local shunt_node_address=$(get_host_ip ipv4 $(config_n_get $shunt_node address) 1)
|
||||
local shunt_node_port=$(config_n_get $shunt_node port)
|
||||
[ "$shunt_node_address" == "$default_node_address" ] && [ "$shunt_node_port" == "$default_node_port" ] && {
|
||||
shunt_proxy=0
|
||||
}
|
||||
}
|
||||
filter_rules "$(config_n_get $proxy_node $shunt_id)" "$stream" "$shunt_proxy" "$proxy_port"
|
||||
}
|
||||
done
|
||||
!
|
||||
else
|
||||
#echolog " - 普通节点(${proxy_type})..."
|
||||
filter_rules "$proxy_node" "$stream"
|
||||
fi
|
||||
}
|
||||
|
||||
dns_hijack() {
|
||||
nft "add rule inet fw4 dstnat ip protocol udp ip dport 53 redirect to 53"
|
||||
echolog "强制转发本机DNS端口 UDP/53 的请求[$?]"
|
||||
}
|
||||
|
||||
add_firewall_rule() {
|
||||
echolog "开始加载防火墙规则..."
|
||||
gen_nftset $NFTSET_LANLIST ipv4_addr $(gen_lanlist)
|
||||
gen_nftset $NFTSET_VPSLIST ipv4_addr
|
||||
gen_nftset $NFTSET_WHITELIST ipv4_addr
|
||||
|
||||
gen_nftset $NFTSET_LANLIST6 ipv6_addr $(gen_lanlist_6)
|
||||
gen_nftset $NFTSET_VPSLIST6 ipv6_addr
|
||||
gen_nftset $NFTSET_WHITELIST6 ipv6_addr
|
||||
|
||||
# 忽略特殊IP段
|
||||
local lan_ifname lan_ip
|
||||
lan_ifname=$(uci -q -p /tmp/state get network.lan.ifname)
|
||||
[ -n "$lan_ifname" ] && {
|
||||
lan_ip=$(ip address show $lan_ifname | grep -w "inet" | awk '{print $2}')
|
||||
lan_ip6=$(ip address show $lan_ifname | grep -w "inet6" | awk '{print $2}')
|
||||
#echolog "本机IPv4网段互访直连:${lan_ip}"
|
||||
#echolog "本机IPv6网段互访直连:${lan_ip6}"
|
||||
|
||||
[ -n "$lan_ip" ] && insert_nftset $NFTSET_LANLIST $(echo $lan_ip | sed -e "s/ /\n/g")
|
||||
[ -n "$lan_ip6" ] && insert_nftset $NFTSET_LANLIST6 $(echo $lan_ip6 | sed -e "s/ /\n/g")
|
||||
}
|
||||
|
||||
[ -n "$ISP_DNS" ] && {
|
||||
#echolog "处理 ISP DNS 例外..."
|
||||
for ispip in $ISP_DNS; do
|
||||
insert_nftset $NFTSET_WHITELIST $ispip >/dev/null 2>&1 &
|
||||
#echolog " - 追加到白名单:${ispip}"
|
||||
done
|
||||
}
|
||||
|
||||
[ -n "$ISP_DNS6" ] && {
|
||||
#echolog "处理 ISP IPv6 DNS 例外..."
|
||||
for ispip6 in $ISP_DNS6; do
|
||||
insert_nftset $NFTSET_WHITELIST6 $ispip6 >/dev/null 2>&1 &
|
||||
#echolog " - 追加到白名单:${ispip6}"
|
||||
done
|
||||
}
|
||||
|
||||
# 过滤所有节点IP
|
||||
filter_vpsip > /dev/null 2>&1 &
|
||||
filter_haproxy > /dev/null 2>&1 &
|
||||
# Prevent some conditions
|
||||
filter_vps_addr $(config_n_get $NODE address) > /dev/null 2>&1 &
|
||||
|
||||
accept_icmp=$(config_t_get global_forwarding accept_icmp 0)
|
||||
accept_icmpv6=$(config_t_get global_forwarding accept_icmpv6 0)
|
||||
|
||||
local tcp_proxy_way=$(config_t_get global_forwarding tcp_proxy_way redirect)
|
||||
if [ "$tcp_proxy_way" = "redirect" ]; then
|
||||
unset is_tproxy
|
||||
nft_prerouting_chain="PSW2_NAT"
|
||||
nft_output_chain="PSW2_OUTPUT_NAT"
|
||||
elif [ "$tcp_proxy_way" = "tproxy" ]; then
|
||||
is_tproxy="TPROXY"
|
||||
nft_prerouting_chain="PSW2_MANGLE"
|
||||
nft_output_chain="PSW2_OUTPUT_MANGLE"
|
||||
fi
|
||||
|
||||
nft "add chain inet fw4 nat_output { type nat hook output priority -1; }"
|
||||
|
||||
nft "add chain inet fw4 PSW2_DIVERT"
|
||||
nft "flush chain inet fw4 PSW2_DIVERT"
|
||||
nft "add rule inet fw4 PSW2_DIVERT meta l4proto tcp socket transparent 1 mark set 1 counter accept"
|
||||
|
||||
nft "add chain inet fw4 PSW2_REDIRECT"
|
||||
nft "flush chain inet fw4 PSW2_REDIRECT"
|
||||
nft "add rule inet fw4 dstnat jump PSW2_REDIRECT"
|
||||
|
||||
# for ipv4 ipv6 tproxy mark
|
||||
nft "add chain inet fw4 PSW2_RULE"
|
||||
nft "flush chain inet fw4 PSW2_RULE"
|
||||
nft "add rule inet fw4 PSW2_RULE meta mark set ct mark counter"
|
||||
nft "add rule inet fw4 PSW2_RULE meta mark 1 counter return"
|
||||
nft "add rule inet fw4 PSW2_RULE tcp flags &(fin|syn|rst|ack) == syn meta mark set mark and 0x0 xor 0x1 counter"
|
||||
nft "add rule inet fw4 PSW2_RULE meta l4proto udp ct state new meta mark set mark and 0x0 xor 0x1 counter"
|
||||
nft "add rule inet fw4 PSW2_RULE ct mark set mark counter"
|
||||
|
||||
#ipv4 tproxy mode and udp
|
||||
nft "add chain inet fw4 PSW2_MANGLE"
|
||||
nft "flush chain inet fw4 PSW2_MANGLE"
|
||||
nft "add rule inet fw4 PSW2_MANGLE ip daddr @$NFTSET_LANLIST counter return"
|
||||
nft "add rule inet fw4 PSW2_MANGLE ip daddr @$NFTSET_VPSLIST counter return"
|
||||
nft "add rule inet fw4 PSW2_MANGLE ip daddr @$NFTSET_WHITELIST counter return"
|
||||
|
||||
nft "add chain inet fw4 PSW2_OUTPUT_MANGLE"
|
||||
nft "flush chain inet fw4 PSW2_OUTPUT_MANGLE"
|
||||
nft "add rule inet fw4 PSW2_OUTPUT_MANGLE ip daddr @$NFTSET_LANLIST counter return"
|
||||
nft "add rule inet fw4 PSW2_OUTPUT_MANGLE ip daddr @$NFTSET_VPSLIST counter return"
|
||||
nft "add rule inet fw4 PSW2_OUTPUT_MANGLE ip daddr @$NFTSET_WHITELIST counter return"
|
||||
nft "add rule inet fw4 PSW2_OUTPUT_MANGLE meta mark 0xff counter return"
|
||||
|
||||
# jump chains
|
||||
nft "add rule inet fw4 mangle_prerouting meta nfproto {ipv4} counter jump PSW2_MANGLE"
|
||||
insert_rule_before "inet fw4" "mangle_prerouting" "PSW2_MANGLE" "counter jump PSW2_DIVERT"
|
||||
|
||||
#ipv4 tcp redirect mode
|
||||
[ -z "${is_tproxy}" ] && {
|
||||
nft "add chain inet fw4 PSW2_NAT"
|
||||
nft "flush chain inet fw4 PSW2_NAT"
|
||||
nft "add rule inet fw4 PSW2_NAT ip daddr @$NFTSET_LANLIST counter return"
|
||||
nft "add rule inet fw4 PSW2_NAT ip daddr @$NFTSET_VPSLIST counter return"
|
||||
nft "add rule inet fw4 PSW2_NAT ip daddr @$NFTSET_WHITELIST counter return"
|
||||
nft "add rule inet fw4 dstnat ip protocol tcp counter jump PSW2_NAT"
|
||||
|
||||
nft "add chain inet fw4 PSW2_OUTPUT_NAT"
|
||||
nft "flush chain inet fw4 PSW2_OUTPUT_NAT"
|
||||
nft "add rule inet fw4 PSW2_OUTPUT_NAT ip daddr @$NFTSET_LANLIST counter return"
|
||||
nft "add rule inet fw4 PSW2_OUTPUT_NAT ip daddr @$NFTSET_VPSLIST counter return"
|
||||
nft "add rule inet fw4 PSW2_OUTPUT_NAT ip daddr @$NFTSET_WHITELIST counter return"
|
||||
nft "add rule inet fw4 PSW2_OUTPUT_NAT meta mark 0xff counter return"
|
||||
}
|
||||
|
||||
#icmp ipv6-icmp redirect
|
||||
if [ "$accept_icmp" = "1" ]; then
|
||||
nft "add chain inet fw4 PSW2_ICMP_REDIRECT"
|
||||
nft "flush chain inet fw4 PSW2_ICMP_REDIRECT"
|
||||
nft "add rule inet fw4 PSW2_ICMP_REDIRECT ip daddr @$NFTSET_LANLIST counter return"
|
||||
nft "add rule inet fw4 PSW2_ICMP_REDIRECT ip daddr @$NFTSET_VPSLIST counter return"
|
||||
nft "add rule inet fw4 PSW2_ICMP_REDIRECT ip daddr @$NFTSET_WHITELIST counter return"
|
||||
|
||||
[ "$accept_icmpv6" = "1" ] && {
|
||||
nft "add rule inet fw4 PSW2_ICMP_REDIRECT ip6 daddr @$NFTSET_LANLIST6 counter return"
|
||||
nft "add rule inet fw4 PSW2_ICMP_REDIRECT ip6 daddr @$NFTSET_VPSLIST6 counter return"
|
||||
nft "add rule inet fw4 PSW2_ICMP_REDIRECT ip6 daddr @$NFTSET_WHITELIST6 counter return"
|
||||
}
|
||||
|
||||
nft "add rule inet fw4 dstnat meta l4proto {icmp,icmpv6} counter jump PSW2_ICMP_REDIRECT"
|
||||
nft "add rule inet fw4 nat_output meta l4proto {icmp,icmpv6} counter jump PSW2_ICMP_REDIRECT"
|
||||
fi
|
||||
|
||||
WAN_IP=$(get_wan_ip)
|
||||
if [ -n "${WAN_IP}" ]; then
|
||||
[ -n "${is_tproxy}" ] && nft "add rule inet fw4 PSW2_MANGLE ip daddr ${WAN_IP} counter return comment \"WAN_IP_RETURN\"" || nft "add rule inet fw4 PSW2_NAT ip daddr ${WAN_IP} counter return comment \"WAN_IP_RETURN\""
|
||||
fi
|
||||
unset WAN_IP
|
||||
|
||||
ip rule add fwmark 1 lookup 100
|
||||
ip route add local 0.0.0.0/0 dev lo table 100
|
||||
|
||||
#ipv6 tproxy mode and udp
|
||||
nft "add chain inet fw4 PSW2_MANGLE_V6"
|
||||
nft "flush chain inet fw4 PSW2_MANGLE_V6"
|
||||
nft "add rule inet fw4 PSW2_MANGLE_V6 ip6 daddr @$NFTSET_LANLIST6 counter return"
|
||||
nft "add rule inet fw4 PSW2_MANGLE_V6 ip6 daddr @$NFTSET_VPSLIST6 counter return"
|
||||
nft "add rule inet fw4 PSW2_MANGLE_V6 ip6 daddr @$NFTSET_WHITELIST6 counter return"
|
||||
|
||||
nft "add chain inet fw4 PSW2_OUTPUT_MANGLE_V6"
|
||||
nft "flush chain inet fw4 PSW2_OUTPUT_MANGLE_V6"
|
||||
nft "add rule inet fw4 PSW2_OUTPUT_MANGLE_V6 ip6 daddr @$NFTSET_LANLIST6 counter return"
|
||||
nft "add rule inet fw4 PSW2_OUTPUT_MANGLE_V6 ip6 daddr @$NFTSET_VPSLIST6 counter return"
|
||||
nft "add rule inet fw4 PSW2_OUTPUT_MANGLE_V6 ip6 daddr @$NFTSET_WHITELIST6 counter return"
|
||||
nft "add rule inet fw4 PSW2_OUTPUT_MANGLE_V6 meta mark 0xff counter return"
|
||||
|
||||
# jump chains
|
||||
[ "$PROXY_IPV6" == "1" ] && {
|
||||
nft "add rule inet fw4 mangle_prerouting meta nfproto {ipv6} counter jump PSW2_MANGLE_V6"
|
||||
nft "add rule inet fw4 mangle_output meta nfproto {ipv6} counter jump PSW2_OUTPUT_MANGLE_V6 comment \"PSW2_OUTPUT_MANGLE\""
|
||||
|
||||
WAN6_IP=$(get_wan6_ip)
|
||||
[ -n "${WAN6_IP}" ] && nft "add rule inet fw4 PSW2_MANGLE_V6 ip6 daddr ${WAN6_IP} counter return comment \"WAN6_IP_RETURN\""
|
||||
unset WAN6_IP
|
||||
|
||||
ip -6 rule add fwmark 1 table 100
|
||||
ip -6 route add local ::/0 dev lo table 100
|
||||
}
|
||||
|
||||
# 过滤Socks节点
|
||||
[ "$SOCKS_ENABLED" = "1" ] && {
|
||||
local ids=$(uci show $CONFIG | grep "=socks" | awk -F '.' '{print $2}' | awk -F '=' '{print $1}')
|
||||
#echolog "分析 Socks 服务所使用节点..."
|
||||
local id enabled node port msg num
|
||||
for id in $ids; do
|
||||
enabled=$(config_n_get $id enabled 0)
|
||||
[ "$enabled" == "1" ] || continue
|
||||
node=$(config_n_get $id node nil)
|
||||
port=$(config_n_get $id port 0)
|
||||
msg="Socks 服务 [:${port}]"
|
||||
if [ "$node" == "nil" ] || [ "$port" == "0" ]; then
|
||||
msg="${msg} 未配置完全,略过"
|
||||
else
|
||||
filter_node $node TCP > /dev/null 2>&1 &
|
||||
filter_node $node UDP > /dev/null 2>&1 &
|
||||
fi
|
||||
#echolog " - ${msg}"
|
||||
done
|
||||
}
|
||||
|
||||
[ "$ENABLED_DEFAULT_ACL" == 1 ] && {
|
||||
# 加载路由器自身代理 TCP
|
||||
if [ "$NODE" != "nil" ] && [ "$LOCALHOST_PROXY" = "1" ]; then
|
||||
echolog "加载路由器自身 TCP 代理..."
|
||||
|
||||
[ "$accept_icmp" = "1" ] && {
|
||||
nft "add rule inet fw4 PSW2_ICMP_REDIRECT meta l4proto icmp ip daddr $FAKE_IP counter redirect"
|
||||
nft "add rule inet fw4 PSW2_ICMP_REDIRECT meta l4proto icmp counter redirect"
|
||||
}
|
||||
|
||||
[ "$accept_icmpv6" = "1" ] && {
|
||||
nft "add rule inet fw4 PSW2_ICMP_REDIRECT meta l4proto icmpv6 ip6 daddr $FAKE_IP_6 counter redirect"
|
||||
nft "add rule inet fw4 PSW2_ICMP_REDIRECT meta l4proto icmpv6 counter redirect"
|
||||
}
|
||||
|
||||
[ -n "${is_tproxy}" ] && {
|
||||
echolog " - 启用 TPROXY 模式"
|
||||
}
|
||||
|
||||
[ "$TCP_NO_REDIR_PORTS" != "disable" ] && {
|
||||
nft "add rule inet fw4 $nft_output_chain ip protocol tcp $(factor $TCP_NO_REDIR_PORTS "tcp dport") counter return"
|
||||
nft "add rule inet fw4 PSW2_OUTPUT_MANGLE_V6 meta l4proto tcp $(factor $TCP_NO_REDIR_PORTS "tcp dport") counter return"
|
||||
echolog " - [$?]不代理TCP 端口:$TCP_NO_REDIR_PORTS"
|
||||
}
|
||||
|
||||
if [ -z "${is_tproxy}" ]; then
|
||||
nft "add rule inet fw4 PSW2_OUTPUT_NAT ip protocol tcp ip daddr $FAKE_IP $(REDIRECT $REDIR_PORT)"
|
||||
nft "add rule inet fw4 PSW2_OUTPUT_NAT ip protocol tcp $(factor $TCP_REDIR_PORTS "tcp dport") $(REDIRECT $REDIR_PORT)"
|
||||
nft "add rule inet fw4 nat_output ip protocol tcp counter jump PSW2_OUTPUT_NAT"
|
||||
else
|
||||
nft "add rule inet fw4 PSW2_OUTPUT_MANGLE ip protocol tcp ip daddr $FAKE_IP counter jump PSW2_RULE"
|
||||
nft "add rule inet fw4 PSW2_OUTPUT_MANGLE ip protocol tcp $(factor $TCP_REDIR_PORTS "tcp dport") jump PSW2_RULE"
|
||||
nft "add rule inet fw4 PSW2_MANGLE meta l4proto tcp iif lo $(REDIRECT $REDIR_PORT TPROXY) comment \"本机\""
|
||||
nft "add rule inet fw4 PSW2_MANGLE ip protocol tcp iif lo counter return comment \"本机\""
|
||||
nft "add rule inet fw4 mangle_output meta nfproto {ipv4} meta l4proto tcp counter jump PSW2_OUTPUT_MANGLE comment \"PSW2_OUTPUT_MANGLE\""
|
||||
fi
|
||||
|
||||
[ "$PROXY_IPV6" == "1" ] && {
|
||||
nft "add rule inet fw4 PSW2_OUTPUT_MANGLE_V6 meta l4proto tcp ip6 daddr $FAKE_IP_6 jump PSW2_RULE"
|
||||
nft "add rule inet fw4 PSW2_OUTPUT_MANGLE_V6 meta l4proto tcp $(factor $TCP_REDIR_PORTS "tcp dport") jump PSW2_RULE"
|
||||
nft "add rule inet fw4 PSW2_MANGLE_V6 meta l4proto tcp iif lo $(REDIRECT $REDIR_PORT TPROXY) comment \"本机\""
|
||||
nft "add rule inet fw4 PSW2_MANGLE_V6 meta l4proto tcp iif lo counter return comment \"本机\""
|
||||
}
|
||||
|
||||
for iface in $IFACES; do
|
||||
nft "insert rule inet fw4 $nft_output_chain ip protocol tcp oif $iface counter return"
|
||||
nft "insert rule inet fw4 PSW2_OUTPUT_MANGLE_V6 ip protocol tcp oif $iface counter return"
|
||||
done
|
||||
fi
|
||||
|
||||
# 处理轮换节点的分流或套娃
|
||||
filter_node $NODE TCP > /dev/null 2>&1 &
|
||||
filter_node $NODE UDP > /dev/null 2>&1 &
|
||||
|
||||
# 加载路由器自身代理 UDP
|
||||
if [ "$NODE" != "nil" ] && [ "$LOCALHOST_PROXY" = "1" ]; then
|
||||
echolog "加载路由器自身 UDP 代理..."
|
||||
|
||||
[ "$UDP_NO_REDIR_PORTS" != "disable" ] && {
|
||||
nft add rule inet fw4 PSW2_OUTPUT_MANGLE ip protocol udp $(factor $UDP_NO_REDIR_PORTS "udp dport") counter return
|
||||
nft add rule inet fw4 PSW2_OUTPUT_MANGLE_V6 meta l4proto udp $(factor $UDP_NO_REDIR_PORTS "udp dport") counter return
|
||||
echolog " - [$?]不代理 UDP 端口:$UDP_NO_REDIR_PORTS"
|
||||
}
|
||||
|
||||
nft "add rule inet fw4 PSW2_OUTPUT_MANGLE ip protocol udp ip daddr $FAKE_IP counter jump PSW2_RULE"
|
||||
nft "add rule inet fw4 PSW2_OUTPUT_MANGLE ip protocol udp $(factor $UDP_REDIR_PORTS "udp dport") jump PSW2_RULE"
|
||||
nft "add rule inet fw4 PSW2_MANGLE meta l4proto udp iif lo $(REDIRECT $REDIR_PORT TPROXY) comment \"本机\""
|
||||
nft "add rule inet fw4 PSW2_MANGLE ip protocol udp iif lo counter return comment \"本机\""
|
||||
nft "add rule inet fw4 mangle_output meta nfproto {ipv4} meta l4proto udp counter jump PSW2_OUTPUT_MANGLE comment \"PSW2_OUTPUT_MANGLE\""
|
||||
|
||||
if [ "$PROXY_IPV6_UDP" == "1" ]; then
|
||||
nft "add rule inet fw4 PSW2_OUTPUT_MANGLE_V6 meta l4proto udp ip6 daddr $FAKE_IP_6 jump PSW2_RULE"
|
||||
nft "add rule inet fw4 PSW2_OUTPUT_MANGLE_V6 meta l4proto udp $(factor $UDP_REDIR_PORTS "udp dport") jump PSW2_RULE"
|
||||
nft "add rule inet fw4 PSW2_MANGLE_V6 meta l4proto udp iif lo $(REDIRECT $REDIR_PORT TPROXY) comment \"本机\""
|
||||
nft "add rule inet fw4 PSW2_MANGLE_V6 meta l4proto udp iif lo counter return comment \"本机\""
|
||||
fi
|
||||
|
||||
for iface in $IFACES; do
|
||||
nft "insert rule inet fw4 $nft_output_chain ip protocol udp oif $iface counter return"
|
||||
nft "insert rule inet fw4 PSW2_OUTPUT_MANGLE_V6 ip protocol udp oif $iface counter return"
|
||||
done
|
||||
fi
|
||||
|
||||
nft "add rule inet fw4 mangle_output oif lo counter return comment \"PSW2_OUTPUT_MANGLE\""
|
||||
nft "add rule inet fw4 mangle_output meta mark 1 counter return comment \"PSW2_OUTPUT_MANGLE\""
|
||||
|
||||
nft "add rule inet fw4 PSW2_MANGLE ip protocol udp udp dport 53 counter return"
|
||||
nft "add rule inet fw4 PSW2_MANGLE_V6 meta l4proto udp udp dport 53 counter return"
|
||||
}
|
||||
|
||||
# 加载ACLS
|
||||
load_acl
|
||||
|
||||
[ -n "${is_tproxy}" -o -n "${udp_flag}" ] && {
|
||||
bridge_nf_ipt=$(sysctl -e -n net.bridge.bridge-nf-call-iptables)
|
||||
echo -n $bridge_nf_ipt > $TMP_PATH/bridge_nf_ipt
|
||||
sysctl -w net.bridge.bridge-nf-call-iptables=0 >/dev/null 2>&1
|
||||
[ "$PROXY_IPV6" == "1" ] && {
|
||||
bridge_nf_ip6t=$(sysctl -e -n net.bridge.bridge-nf-call-ip6tables)
|
||||
echo -n $bridge_nf_ip6t > $TMP_PATH/bridge_nf_ip6t
|
||||
sysctl -w net.bridge.bridge-nf-call-ip6tables=0 >/dev/null 2>&1
|
||||
}
|
||||
}
|
||||
echolog "防火墙规则加载完成!"
|
||||
}
|
||||
|
||||
del_firewall_rule() {
|
||||
for nft in "forward" "dstnat" "srcnat" "nat_output" "mangle_prerouting" "mangle_output"; do
|
||||
local handles=$(nft -a list chain inet fw4 ${nft} 2>/dev/null | grep -E "PSW2_" | awk -F '# handle ' '{print$2}')
|
||||
for handle in $handles; do
|
||||
nft delete rule inet fw4 ${nft} handle ${handle} 2>/dev/null
|
||||
done
|
||||
done
|
||||
|
||||
for handle in $(nft -a list chains | grep -E "chain PSW2_" | grep -v "PSW2_RULE" | awk -F '# handle ' '{print$2}'); do
|
||||
nft delete chain inet fw4 handle ${handle} 2>/dev/null
|
||||
done
|
||||
|
||||
# Need to be removed at the end, otherwise it will show "Resource busy"
|
||||
nft delete chain inet fw4 handle $(nft -a list chains | grep -E "PSW2_RULE" | awk -F '# handle ' '{print$2}') 2>/dev/null
|
||||
|
||||
ip rule del fwmark 1 lookup 100 2>/dev/null
|
||||
ip route del local 0.0.0.0/0 dev lo table 100 2>/dev/null
|
||||
|
||||
ip -6 rule del fwmark 1 table 100 2>/dev/null
|
||||
ip -6 route del local ::/0 dev lo table 100 2>/dev/null
|
||||
|
||||
destroy_nftset $NFTSET_LANLIST
|
||||
destroy_nftset $NFTSET_VPSLIST
|
||||
destroy_nftset $NFTSET_WHITELIST
|
||||
|
||||
destroy_nftset $NFTSET_LANLIST6
|
||||
destroy_nftset $NFTSET_VPSLIST6
|
||||
destroy_nftset $NFTSET_WHITELIST6
|
||||
|
||||
$DIR/app.sh echolog "删除相关防火墙规则完成。"
|
||||
}
|
||||
|
||||
flush_nftset() {
|
||||
del_firewall_rule
|
||||
destroy_nftset $NFTSET_VPSLIST $NFTSET_WHITELIST $NFTSET_LANLIST
|
||||
destroy_nftset $NFTSET_VPSLIST6 $NFTSET_WHITELIST6 $NFTSET_LANLIST6
|
||||
/etc/init.d/passwall2 reload
|
||||
}
|
||||
|
||||
flush_include() {
|
||||
echo '#!/bin/sh' >$FWI
|
||||
}
|
||||
|
||||
gen_include() {
|
||||
local nft_chain_file=$TMP_PATH/PSW2_RULE.nft
|
||||
local nft_set_file=$TMP_PATH/PSW2_SETS.nft
|
||||
echo "#!/usr/sbin/nft -f" > $nft_chain_file
|
||||
echo "#!/usr/sbin/nft -f" > $nft_set_file
|
||||
for chain in $(nft -a list chains | grep -E "chain PSW2_" | awk -F ' ' '{print$2}'); do
|
||||
nft list chain inet fw4 ${chain} >> $nft_chain_file
|
||||
done
|
||||
|
||||
for set_name in $(nft -a list sets | grep -E "set passwall2_" | awk -F ' ' '{print$2}'); do
|
||||
nft list set inet fw4 ${set_name} >> $nft_set_file
|
||||
done
|
||||
|
||||
local __nft=" "
|
||||
__nft=$(cat <<- EOF
|
||||
|
||||
[ -z "\$(nft list sets 2>/dev/null | grep "passwall2_")" ] && nft -f ${nft_set_file}
|
||||
[ -z "\$(nft list chain inet fw4 nat_output 2>/dev/null)" ] && nft "add chain inet fw4 nat_output { type nat hook output priority -1; }"
|
||||
nft -f ${nft_chain_file}
|
||||
|
||||
nft "add rule inet fw4 dstnat jump PSW2_REDIRECT"
|
||||
|
||||
[ "$accept_icmp" == "1" ] && {
|
||||
nft "add rule inet fw4 dstnat meta l4proto {icmp,icmpv6} counter jump PSW2_ICMP_REDIRECT"
|
||||
nft "add rule inet fw4 nat_output meta l4proto {icmp,icmpv6} counter jump PSW2_ICMP_REDIRECT"
|
||||
}
|
||||
|
||||
[ -z "${is_tproxy}" ] && {
|
||||
PR_INDEX=\$(sh ${MY_PATH} RULE_LAST_INDEX "inet fw4" PSW2_NAT WAN_IP_RETURN -1)
|
||||
if [ \$PR_INDEX -ge 0 ]; then
|
||||
WAN_IP=\$(sh ${MY_PATH} get_wan_ip)
|
||||
[ ! -z "\${WAN_IP}" ] && nft "replace rule inet fw4 PSW2_NAT handle \$PR_INDEX ip daddr "\${WAN_IP}" counter return comment \"WAN_IP_RETURN\""
|
||||
fi
|
||||
nft "add rule inet fw4 dstnat ip protocol tcp counter jump PSW2_NAT"
|
||||
nft "add rule inet fw4 nat_output ip protocol tcp counter jump PSW2_OUTPUT_NAT"
|
||||
}
|
||||
|
||||
[ -n "${is_tproxy}" ] && {
|
||||
PR_INDEX=\$(sh ${MY_PATH} RULE_LAST_INDEX "inet fw4" PSW2_MANGLE WAN_IP_RETURN -1)
|
||||
if [ \$PR_INDEX -ge 0 ]; then
|
||||
WAN_IP=\$(sh ${MY_PATH} get_wan_ip)
|
||||
[ ! -z "\${WAN_IP}" ] && nft "replace rule inet fw4 PSW2_MANGLE handle \$PR_INDEX ip daddr "\${WAN_IP}" counter return comment \"WAN_IP_RETURN\""
|
||||
fi
|
||||
nft "add rule inet fw4 mangle_prerouting meta nfproto {ipv4} counter jump PSW2_MANGLE"
|
||||
nft "add rule inet fw4 mangle_output meta nfproto {ipv4} meta l4proto tcp counter jump PSW2_OUTPUT_MANGLE comment \"PSW2_OUTPUT_MANGLE\""
|
||||
}
|
||||
\$(sh ${MY_PATH} insert_rule_before "inet fw4" "mangle_prerouting" "PSW2_MANGLE" "counter jump PSW2_DIVERT")
|
||||
|
||||
[ "$UDP_NODE" != "nil" -o "$TCP_UDP" = "1" ] && nft "add rule inet fw4 mangle_output meta nfproto {ipv4} meta l4proto udp counter jump PSW2_OUTPUT_MANGLE comment \"PSW2_OUTPUT_MANGLE\""
|
||||
|
||||
[ "$PROXY_IPV6" == "1" ] && {
|
||||
PR_INDEX=\$(sh ${MY_PATH} RULE_LAST_INDEX "inet fw4" PSW2_MANGLE_V6 WAN6_IP_RETURN -1)
|
||||
if [ \$PR_INDEX -ge 0 ]; then
|
||||
WAN6_IP=\$(sh ${MY_PATH} get_wan6_ip)
|
||||
[ ! -z "\${WAN_IP}" ] && nft "replace rule inet fw4 PSW2_MANGLE_V6 handle \$PR_INDEX ip6 daddr "\${WAN6_IP}" counter return comment \"WAN6_IP_RETURN\""
|
||||
fi
|
||||
nft "add rule inet fw4 mangle_prerouting meta nfproto {ipv6} counter jump PSW2_MANGLE_V6"
|
||||
nft "add rule inet fw4 mangle_output meta nfproto {ipv6} counter jump PSW2_OUTPUT_MANGLE_V6 comment \"PSW2_OUTPUT_MANGLE\""
|
||||
}
|
||||
|
||||
nft "add rule inet fw4 mangle_output oif lo counter return comment \"PSW2_OUTPUT_MANGLE\""
|
||||
nft "add rule inet fw4 mangle_output meta mark 1 counter return comment \"PSW2_OUTPUT_MANGLE\""
|
||||
EOF
|
||||
)
|
||||
|
||||
cat <<-EOF >> $FWI
|
||||
${__nft}
|
||||
EOF
|
||||
return 0
|
||||
}
|
||||
|
||||
start() {
|
||||
[ "$ENABLED_DEFAULT_ACL" == 0 -a "$ENABLED_ACLS" == 0 ] && return
|
||||
add_firewall_rule
|
||||
gen_include
|
||||
}
|
||||
|
||||
stop() {
|
||||
del_firewall_rule
|
||||
flush_include
|
||||
}
|
||||
|
||||
arg1=$1
|
||||
shift
|
||||
case $arg1 in
|
||||
RULE_LAST_INDEX)
|
||||
RULE_LAST_INDEX "$@"
|
||||
;;
|
||||
insert_rule_before)
|
||||
insert_rule_before "$@"
|
||||
;;
|
||||
insert_rule_after)
|
||||
insert_rule_after "$@"
|
||||
;;
|
||||
flush_nftset)
|
||||
flush_nftset
|
||||
;;
|
||||
get_wan_ip)
|
||||
get_wan_ip
|
||||
;;
|
||||
get_wan6_ip)
|
||||
get_wan6_ip
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
start)
|
||||
start
|
||||
;;
|
||||
*) ;;
|
||||
esac
|
194
luci-app-passwall2/root/usr/share/passwall2/rule_update.lua
Executable file
194
luci-app-passwall2/root/usr/share/passwall2/rule_update.lua
Executable file
@ -0,0 +1,194 @@
|
||||
#!/usr/bin/lua
|
||||
|
||||
require 'nixio'
|
||||
require 'luci.sys'
|
||||
local luci = luci
|
||||
local ucic = luci.model.uci.cursor()
|
||||
local jsonc = require "luci.jsonc"
|
||||
local name = 'passwall2'
|
||||
local api = require "luci.passwall2.api"
|
||||
local arg1 = arg[1]
|
||||
|
||||
local reboot = 0
|
||||
local geoip_update = 0
|
||||
local geosite_update = 0
|
||||
local v2ray_asset_location = ucic:get_first(name, 'global_rules', "v2ray_location_asset", "/usr/share/v2ray/")
|
||||
|
||||
-- Custom geo file
|
||||
local geoip_api = ucic:get_first(name, 'global_rules', "geoip_url", "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest")
|
||||
local geosite_api = ucic:get_first(name, 'global_rules', "geosite_url", "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest")
|
||||
--
|
||||
local use_nft = ucic:get(name, "@global_forwarding[0]", "use_nft") or "0"
|
||||
|
||||
local log = function(...)
|
||||
if arg1 then
|
||||
if arg1 == "log" then
|
||||
api.log(...)
|
||||
elseif arg1 == "print" then
|
||||
local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ")
|
||||
print(result)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- curl
|
||||
local function curl(url, file)
|
||||
local args = {
|
||||
"-skL", "-w %{http_code}", "--retry 3", "--connect-timeout 3"
|
||||
}
|
||||
if file then
|
||||
args[#args + 1] = "-o " .. file
|
||||
end
|
||||
local return_code, result = api.curl_logic(url, nil, args)
|
||||
return tonumber(result)
|
||||
end
|
||||
|
||||
--获取geoip
|
||||
local function fetch_geoip()
|
||||
--请求geoip
|
||||
xpcall(function()
|
||||
local return_code, content = api.curl_logic(geoip_api)
|
||||
local json = jsonc.parse(content)
|
||||
if json.tag_name and json.assets then
|
||||
for _, v in ipairs(json.assets) do
|
||||
if v.name and v.name == "geoip.dat.sha256sum" then
|
||||
local sret = curl(v.browser_download_url, "/tmp/geoip.dat.sha256sum")
|
||||
if sret == 200 then
|
||||
local f = io.open("/tmp/geoip.dat.sha256sum", "r")
|
||||
local content = f:read()
|
||||
f:close()
|
||||
f = io.open("/tmp/geoip.dat.sha256sum", "w")
|
||||
f:write(content:gsub("geoip.dat", "/tmp/geoip.dat"), "")
|
||||
f:close()
|
||||
|
||||
if nixio.fs.access(v2ray_asset_location .. "geoip.dat") then
|
||||
luci.sys.call(string.format("cp -f %s %s", v2ray_asset_location .. "geoip.dat", "/tmp/geoip.dat"))
|
||||
if luci.sys.call('sha256sum -c /tmp/geoip.dat.sha256sum > /dev/null 2>&1') == 0 then
|
||||
log("geoip 版本一致,无需更新。")
|
||||
return 1
|
||||
end
|
||||
end
|
||||
for _2, v2 in ipairs(json.assets) do
|
||||
if v2.name and v2.name == "geoip.dat" then
|
||||
sret = curl(v2.browser_download_url, "/tmp/geoip.dat")
|
||||
if luci.sys.call('sha256sum -c /tmp/geoip.dat.sha256sum > /dev/null 2>&1') == 0 then
|
||||
luci.sys.call(string.format("mkdir -p %s && cp -f %s %s", v2ray_asset_location, "/tmp/geoip.dat", v2ray_asset_location .. "geoip.dat"))
|
||||
reboot = 1
|
||||
log("geoip 更新成功。")
|
||||
return 1
|
||||
else
|
||||
log("geoip 更新失败,请稍后再试。")
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
function(e)
|
||||
end)
|
||||
|
||||
return 0
|
||||
end
|
||||
|
||||
--获取geosite
|
||||
local function fetch_geosite()
|
||||
--请求geosite
|
||||
xpcall(function()
|
||||
local return_code, content = api.curl_logic(geosite_api)
|
||||
local json = jsonc.parse(content)
|
||||
if json.tag_name and json.assets then
|
||||
for _, v in ipairs(json.assets) do
|
||||
if v.name and v.name == "geosite.dat.sha256sum" then
|
||||
local sret = curl(v.browser_download_url, "/tmp/geosite.dat.sha256sum")
|
||||
if sret == 200 then
|
||||
local f = io.open("/tmp/geosite.dat.sha256sum", "r")
|
||||
local content = f:read()
|
||||
f:close()
|
||||
f = io.open("/tmp/geosite.dat.sha256sum", "w")
|
||||
f:write(content:gsub("geosite.dat", "/tmp/geosite.dat"), "")
|
||||
f:close()
|
||||
|
||||
if nixio.fs.access(v2ray_asset_location .. "geosite.dat") then
|
||||
luci.sys.call(string.format("cp -f %s %s", v2ray_asset_location .. "geosite.dat", "/tmp/geosite.dat"))
|
||||
if luci.sys.call('sha256sum -c /tmp/geosite.dat.sha256sum > /dev/null 2>&1') == 0 then
|
||||
log("geosite 版本一致,无需更新。")
|
||||
return 1
|
||||
end
|
||||
end
|
||||
for _2, v2 in ipairs(json.assets) do
|
||||
if v2.name and v2.name == "geosite.dat" then
|
||||
sret = curl(v2.browser_download_url, "/tmp/geosite.dat")
|
||||
if luci.sys.call('sha256sum -c /tmp/geosite.dat.sha256sum > /dev/null 2>&1') == 0 then
|
||||
luci.sys.call(string.format("mkdir -p %s && cp -f %s %s", v2ray_asset_location, "/tmp/geosite.dat", v2ray_asset_location .. "geosite.dat"))
|
||||
reboot = 1
|
||||
log("geosite 更新成功。")
|
||||
return 1
|
||||
else
|
||||
log("geosite 更新失败,请稍后再试。")
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
function(e)
|
||||
end)
|
||||
|
||||
return 0
|
||||
end
|
||||
|
||||
if arg[2] then
|
||||
string.gsub(arg[2], '[^' .. "," .. ']+', function(w)
|
||||
if w == "geoip" then
|
||||
geoip_update = 1
|
||||
end
|
||||
if w == "geosite" then
|
||||
geosite_update = 1
|
||||
end
|
||||
end)
|
||||
else
|
||||
geoip_update = ucic:get_first(name, 'global_rules', "geoip_update", 1)
|
||||
geosite_update = ucic:get_first(name, 'global_rules', "geosite_update", 1)
|
||||
end
|
||||
if geoip_update == 0 and geosite_update == 0 then
|
||||
os.exit(0)
|
||||
end
|
||||
|
||||
log("开始更新规则...")
|
||||
|
||||
if tonumber(geoip_update) == 1 then
|
||||
log("geoip 开始更新...")
|
||||
local status = fetch_geoip()
|
||||
os.remove("/tmp/geoip.dat")
|
||||
os.remove("/tmp/geoip.dat.sha256sum")
|
||||
end
|
||||
|
||||
if tonumber(geosite_update) == 1 then
|
||||
log("geosite 开始更新...")
|
||||
local status = fetch_geosite()
|
||||
os.remove("/tmp/geosite.dat")
|
||||
os.remove("/tmp/geosite.dat.sha256sum")
|
||||
end
|
||||
|
||||
ucic:set(name, ucic:get_first(name, 'global_rules'), "geoip_update", geoip_update)
|
||||
ucic:set(name, ucic:get_first(name, 'global_rules'), "geosite_update", geosite_update)
|
||||
ucic:save(name)
|
||||
luci.sys.call("uci commit " .. name)
|
||||
|
||||
if reboot == 1 then
|
||||
log("重启服务,应用新的规则。")
|
||||
if use_nft == "1" then
|
||||
luci.sys.call("sh /usr/share/" .. name .. "/nftables.sh flush_nftset > /dev/null 2>&1 &")
|
||||
else
|
||||
luci.sys.call("sh /usr/share/" .. name .. "/iptables.sh flush_ipset > /dev/null 2>&1 &")
|
||||
end
|
||||
end
|
||||
log("规则更新完毕...")
|
1173
luci-app-passwall2/root/usr/share/passwall2/subscribe.lua
Executable file
1173
luci-app-passwall2/root/usr/share/passwall2/subscribe.lua
Executable file
File diff suppressed because it is too large
Load Diff
253
luci-app-passwall2/root/usr/share/passwall2/test.sh
Executable file
253
luci-app-passwall2/root/usr/share/passwall2/test.sh
Executable file
@ -0,0 +1,253 @@
|
||||
#!/bin/sh
|
||||
|
||||
CONFIG=passwall2
|
||||
LOG_FILE=/tmp/log/$CONFIG.log
|
||||
LOCK_FILE_DIR=/tmp/lock
|
||||
LOCK_FILE=${LOCK_FILE_DIR}/${CONFIG}_script.lock
|
||||
|
||||
echolog() {
|
||||
local d="$(date "+%Y-%m-%d %H:%M:%S")"
|
||||
#echo -e "$d: $1"
|
||||
echo -e "$d: $1" >> $LOG_FILE
|
||||
}
|
||||
|
||||
config_n_get() {
|
||||
local ret=$(uci -q get "${CONFIG}.${1}.${2}" 2>/dev/null)
|
||||
echo "${ret:=$3}"
|
||||
}
|
||||
|
||||
config_t_get() {
|
||||
local index=0
|
||||
[ -n "$4" ] && index=$4
|
||||
local ret=$(uci -q get $CONFIG.@$1[$index].$2 2>/dev/null)
|
||||
echo ${ret:=$3}
|
||||
}
|
||||
|
||||
test_url() {
|
||||
local url=$1
|
||||
local try=1
|
||||
[ -n "$2" ] && try=$2
|
||||
local timeout=2
|
||||
[ -n "$3" ] && timeout=$3
|
||||
local extra_params=$4
|
||||
curl --help all | grep "\-\-retry-all-errors" > /dev/null
|
||||
[ $? == 0 ] && extra_params="--retry-all-errors ${extra_params}"
|
||||
status=$(/usr/bin/curl -I -o /dev/null -skL $extra_params --connect-timeout ${timeout} --retry ${try} -w %{http_code} "$url")
|
||||
case "$status" in
|
||||
204|\
|
||||
200)
|
||||
status=200
|
||||
;;
|
||||
esac
|
||||
echo $status
|
||||
}
|
||||
|
||||
test_proxy() {
|
||||
result=0
|
||||
status=$(test_url "https://www.google.com/generate_204" ${retry_num} ${connect_timeout})
|
||||
if [ "$status" = "200" ]; then
|
||||
result=0
|
||||
else
|
||||
status2=$(test_url "https://www.baidu.com" ${retry_num} ${connect_timeout})
|
||||
if [ "$status2" = "200" ]; then
|
||||
result=1
|
||||
else
|
||||
result=2
|
||||
ping -c 3 -W 1 223.5.5.5 > /dev/null 2>&1
|
||||
[ $? -eq 0 ] && {
|
||||
result=1
|
||||
}
|
||||
fi
|
||||
fi
|
||||
echo $result
|
||||
}
|
||||
|
||||
url_test_node() {
|
||||
result=0
|
||||
local node_id=$1
|
||||
local _type=$(echo $(config_n_get ${node_id} type nil) | tr 'A-Z' 'a-z')
|
||||
[ "${_type}" != "nil" ] && {
|
||||
local _tmp_port=$(/usr/share/${CONFIG}/app.sh get_new_port 61080 tcp,udp)
|
||||
/usr/share/${CONFIG}/app.sh run_socks flag="url_test_${node_id}" node=${node_id} bind=127.0.0.1 socks_port=${_tmp_port} config_file=url_test_${node_id}.json
|
||||
local curlx="socks5h://127.0.0.1:${_tmp_port}"
|
||||
sleep 1s
|
||||
result=$(curl --connect-timeout 3 -o /dev/null -I -skL -w "%{http_code}:%{time_starttransfer}" -x $curlx "https://www.google.com/generate_204")
|
||||
pgrep -af "url_test_${node_id}" | awk '! /test\.sh/{print $1}' | xargs kill -9 >/dev/null 2>&1
|
||||
rm -rf "/tmp/etc/${CONFIG}/url_test_${node_id}.json"
|
||||
}
|
||||
echo $result
|
||||
}
|
||||
|
||||
test_node() {
|
||||
local node_id=$1
|
||||
local _type=$(echo $(config_n_get ${node_id} type nil) | tr 'A-Z' 'a-z')
|
||||
[ "${_type}" != "nil" ] && {
|
||||
local _tmp_port=$(/usr/share/${CONFIG}/app.sh get_new_port 61080 tcp,udp)
|
||||
/usr/share/${CONFIG}/app.sh run_socks flag="test_node_${node_id}" node=${node_id} bind=127.0.0.1 socks_port=${_tmp_port} config_file=test_node_${node_id}.json
|
||||
local curlx="socks5h://127.0.0.1:${_tmp_port}"
|
||||
sleep 1s
|
||||
_proxy_status=$(test_url "https://www.google.com/generate_204" ${retry_num} ${connect_timeout} "-x $curlx")
|
||||
pgrep -af "test_node_${node_id}" | awk '! /test\.sh/{print $1}' | xargs kill -9 >/dev/null 2>&1
|
||||
rm -rf "/tmp/etc/${CONFIG}/test_node_${node_id}.json"
|
||||
if [ "${_proxy_status}" -eq 200 ]; then
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
flag=0
|
||||
main_node=$(config_t_get global node nil)
|
||||
|
||||
test_auto_switch() {
|
||||
flag=$(expr $flag + 1)
|
||||
local TYPE=$1
|
||||
local b_nodes=$2
|
||||
local now_node=$3
|
||||
[ -z "$now_node" ] && {
|
||||
local f="/tmp/etc/$CONFIG/id/global"
|
||||
if [ -f "${f}" ]; then
|
||||
now_node=$(cat ${f})
|
||||
if [ "$(config_n_get $now_node protocol nil)" = "_shunt" ]; then
|
||||
if [ "$shunt_logic" == "1" ] && [ -f "${f}_default" ]; then
|
||||
now_node=$(cat ${f}_default)
|
||||
elif [ "$shunt_logic" == "2" ] && [ -f "${f}_main" ]; then
|
||||
now_node=$(cat ${f}_main)
|
||||
else
|
||||
shunt_logic=0
|
||||
fi
|
||||
else
|
||||
shunt_logic=0
|
||||
fi
|
||||
else
|
||||
#echolog "自动切换检测:未知错误"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
[ $flag -le 1 ] && {
|
||||
main_node=$now_node
|
||||
}
|
||||
|
||||
status=$(test_proxy)
|
||||
if [ "$status" == 2 ]; then
|
||||
echolog "自动切换检测:无法连接到网络,请检查网络是否正常!"
|
||||
return 2
|
||||
fi
|
||||
|
||||
#检测主节点是否能使用
|
||||
if [ "$restore_switch" == "1" ] && [ "$main_node" != "nil" ] && [ "$now_node" != "$main_node" ]; then
|
||||
test_node ${main_node}
|
||||
[ $? -eq 0 ] && {
|
||||
#主节点正常,切换到主节点
|
||||
echolog "自动切换检测:${TYPE}主节点【$(config_n_get $main_node type):[$(config_n_get $main_node remarks)]】正常,切换到主节点!"
|
||||
/usr/share/${CONFIG}/app.sh node_switch flag=global new_node=${main_node} shunt_logic=${shunt_logic}
|
||||
[ $? -eq 0 ] && {
|
||||
echolog "自动切换检测:${TYPE}节点切换完毕!"
|
||||
[ "$shunt_logic" != "0" ] && {
|
||||
local node=$(config_t_get global node nil)
|
||||
[ "$(config_n_get $node protocol nil)" = "_shunt" ] && {
|
||||
if [ "$shunt_logic" == "1" ]; then
|
||||
uci set $CONFIG.$node.default_node="$main_node"
|
||||
elif [ "$shunt_logic" == "2" ]; then
|
||||
uci set $CONFIG.$node.main_node="$main_node"
|
||||
fi
|
||||
uci commit $CONFIG
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
fi
|
||||
|
||||
if [ "$status" == 0 ]; then
|
||||
#echolog "自动切换检测:${TYPE}节点【$(config_n_get $now_node type):[$(config_n_get $now_node remarks)]】正常。"
|
||||
return 0
|
||||
elif [ "$status" == 1 ]; then
|
||||
echolog "自动切换检测:${TYPE}节点【$(config_n_get $now_node type):[$(config_n_get $now_node remarks)]】异常,切换到下一个备用节点检测!"
|
||||
local new_node
|
||||
in_backup_nodes=$(echo $b_nodes | grep $now_node)
|
||||
# 判断当前节点是否存在于备用节点列表里
|
||||
if [ -z "$in_backup_nodes" ]; then
|
||||
# 如果不存在,设置第一个节点为新的节点
|
||||
new_node=$(echo $b_nodes | awk -F ' ' '{print $1}')
|
||||
else
|
||||
# 如果存在,设置下一个备用节点为新的节点
|
||||
#local count=$(expr $(echo $b_nodes | grep -o ' ' | wc -l) + 1)
|
||||
local next_node=$(echo $b_nodes | awk -F "$now_node" '{print $2}' | awk -F " " '{print $1}')
|
||||
if [ -z "$next_node" ]; then
|
||||
new_node=$(echo $b_nodes | awk -F ' ' '{print $1}')
|
||||
else
|
||||
new_node=$next_node
|
||||
fi
|
||||
fi
|
||||
test_node ${new_node}
|
||||
if [ $? -eq 0 ]; then
|
||||
[ "$restore_switch" == "0" ] && {
|
||||
[ "$shunt_logic" == "0" ] && uci set $CONFIG.@global[0].node=$new_node
|
||||
[ -z "$(echo $b_nodes | grep $main_node)" ] && uci add_list $CONFIG.@auto_switch[0].node=$main_node
|
||||
uci commit $CONFIG
|
||||
}
|
||||
echolog "自动切换检测:${TYPE}节点【$(config_n_get $new_node type):[$(config_n_get $new_node remarks)]】正常,切换到此节点!"
|
||||
/usr/share/${CONFIG}/app.sh node_switch flag=global new_node=${new_node} shunt_logic=${shunt_logic}
|
||||
[ $? -eq 0 ] && {
|
||||
[ "$restore_switch" == "1" ] && [ "$shunt_logic" != "0" ] && {
|
||||
local node=$(config_t_get global node nil)
|
||||
[ "$(config_n_get $node protocol nil)" = "_shunt" ] && {
|
||||
if [ "$shunt_logic" == "1" ]; then
|
||||
uci set $CONFIG.$node.default_node="$main_node"
|
||||
elif [ "$shunt_logic" == "2" ]; then
|
||||
uci set $CONFIG.$node.main_node="$main_node"
|
||||
fi
|
||||
uci commit $CONFIG
|
||||
}
|
||||
}
|
||||
echolog "自动切换检测:${TYPE}节点切换完毕!"
|
||||
}
|
||||
return 0
|
||||
else
|
||||
test_auto_switch ${TYPE} "${b_nodes}" ${new_node}
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
start() {
|
||||
ENABLED=$(config_t_get global enabled 0)
|
||||
[ "$ENABLED" != 1 ] && return 1
|
||||
ENABLED=$(config_t_get auto_switch enable 0)
|
||||
[ "$ENABLED" != 1 ] && return 1
|
||||
delay=$(config_t_get auto_switch testing_time 1)
|
||||
#sleep 9s
|
||||
connect_timeout=$(config_t_get auto_switch connect_timeout 3)
|
||||
retry_num=$(config_t_get auto_switch retry_num 3)
|
||||
restore_switch=$(config_t_get auto_switch restore_switch 0)
|
||||
shunt_logic=$(config_t_get auto_switch shunt_logic 0)
|
||||
while [ "$ENABLED" -eq 1 ]; do
|
||||
[ -f "$LOCK_FILE" ] && {
|
||||
sleep 6s
|
||||
continue
|
||||
}
|
||||
touch $LOCK_FILE
|
||||
NODE=$(config_t_get auto_switch node nil)
|
||||
[ -n "$NODE" -a "$NODE" != "nil" ] && {
|
||||
NODE=$(echo $NODE | tr -s ' ' '\n' | uniq | tr -s '\n' ' ')
|
||||
test_auto_switch TCP "$NODE"
|
||||
}
|
||||
rm -f $LOCK_FILE
|
||||
sleep ${delay}m
|
||||
done
|
||||
}
|
||||
|
||||
arg1=$1
|
||||
shift
|
||||
case $arg1 in
|
||||
test_url)
|
||||
test_url $@
|
||||
;;
|
||||
url_test_node)
|
||||
url_test_node $@
|
||||
;;
|
||||
*)
|
||||
start
|
||||
;;
|
||||
esac
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"luci-app-passwall2": {
|
||||
"description": "Grant UCI access for luci-app-passwall2",
|
||||
"read": {
|
||||
"uci": [ "passwall2", "passwall2_server" ]
|
||||
},
|
||||
"write": {
|
||||
"uci": [ "passwall2", "passwall2_server" ]
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user