This commit is contained in:
sbwml 2022-04-17 03:50:24 +08:00
commit 12e3f6f41c
212 changed files with 165145 additions and 0 deletions

11
README.md Normal file
View File

@ -0,0 +1,11 @@
# Psswall & SSRP 插件防炸上游备份
## 所需依赖
```shell
svn co https://github.com/immortalwrt/packages/trunk/net/dns2socks package/helloworld-deps/dns2socks
svn co https://github.com/immortalwrt/packages/trunk/net/microsocks package/helloworld-deps/microsocks
svn co https://github.com/immortalwrt/packages/trunk/net/ipt2socks package/helloworld-deps/ipt2socks
svn co https://github.com/immortalwrt/packages/trunk/net/pdnsd-alt package/helloworld-deps/pdnsd
svn co https://github.com/immortalwrt/packages/trunk/net/redsocks2 package/helloworld-deps/redsocks2
```

44
brook/Makefile Normal file
View File

@ -0,0 +1,44 @@
# SPDX-License-Identifier: GPL-3.0-only
#
# Copyright (C) 2021 ImmortalWrt.org
include $(TOPDIR)/rules.mk
PKG_NAME:=brook
PKG_VERSION:=20220406
PKG_RELEASE:=$(AUTORELEASE)
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/txthinking/brook/tar.gz/v$(PKG_VERSION)?
PKG_HASH:=cf4433263cc755edfe56be66d206b7ee5083faaaa8b30bb4102174ad73e22764
PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
PKG_LICENSE:=GPL-3.0
PKG_LICENSE_FILES:=LICENSE
PKG_BUILD_DEPENDS:=golang/host
PKG_BUILD_PARALLEL:=1
PKG_USE_MIPS16:=0
GO_PKG:=github.com/txthinking/brook
GO_PKG_BUILD_PKG:=$(GO_PKG)/cli/brook
include $(INCLUDE_DIR)/package.mk
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
define Package/brook
SECTION:=net
CATEGORY:=Network
SUBMENU:=Web Servers/Proxies
TITLE:=A cross-platform proxy software
DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle
URL:=https://github.com/txthinking/brook
endef
define Package/brook/description
Brook is a cross-platform strong encryption and not detectable proxy.
Zero-Configuration.
endef
$(eval $(call GoBinPackage,brook))
$(eval $(call BuildPackage,brook))

40
chinadns-ng/Makefile Normal file
View File

@ -0,0 +1,40 @@
# SPDX-License-Identifier: GPL-3.0-only
#
# Copyright (C) 2021 ImmortalWrt.org
include $(TOPDIR)/rules.mk
PKG_NAME:=chinadns-ng
PKG_VERSION:=1.0-beta.25
PKG_RELEASE:=$(AUTORELEASE)
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://github.com/zfl9/chinadns-ng.git
PKG_SOURCE_DATE:=2021-05-08
PKG_SOURCE_VERSION:=14cc6348d67b09cae37d9bce554c89c2c0e0b265
PKG_MIRROR_HASH:=3b66fc0888d9488e3b8e39df3016d51fae1b43325d292381e94aa3c7d2318282
PKG_LICENSE:=AGPL-3.0-only
PKG_LICENSE_FILES:=LICENSE
PKG_MAINTAINER:=pexcn <i@pexcn.me>
PKG_BUILD_PARALLEL:=1
PKG_INSTALL:=1
include $(INCLUDE_DIR)/package.mk
define Package/chinadns-ng
SECTION:=net
CATEGORY:=Network
SUBMENU:=IP Addresses and Names
TITLE:=ChinaDNS next generation, refactoring with epoll and ipset.
URL:=https://github.com/zfl9/chinadns-ng
DEPENDS:=+ipset
endef
define Package/chinadns-ng/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/chinadns-ng $(1)/usr/bin
endef
$(eval $(call BuildPackage,chinadns-ng))

53
hysteria/Makefile Normal file
View File

@ -0,0 +1,53 @@
# SPDX-License-Identifier: GPL-3.0-only
#
# Copyright (C) 2021 ImmortalWrt.org
include $(TOPDIR)/rules.mk
PKG_NAME:=hysteria
PKG_VERSION:=1.0.3
PKG_RELEASE:=$(AUTORELEASE)
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/HyNetwork/hysteria/tar.gz/v$(PKG_VERSION)?
PKG_HASH:=aa187a860d517404ec2293603c8ad47c4d06776a0e621befb0bc769c3d93779d
PKG_LICENSE:=MIT
PKG_LICENSE_FILE:=LICENSE
PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
PKG_BUILD_DEPENDS:=golang/host
PKG_BUILD_PARALLEL:=1
PKG_USE_MIPS16:=0
GO_PKG:=github.com/tobyxdd/hysteria
GO_PKG_BUILD_PKG:=$(GO_PKG)/cmd
GO_PKG_LDFLAGS_X:=main.appVersion=$(PKG_VERSION)
include $(INCLUDE_DIR)/package.mk
include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk
define Package/hysteria
SECTION:=net
CATEGORY:=Network
TITLE:=A feature-packed network utility optimized for networks of poor quality
URL:=https://github.com/tobyxdd/hysteria
DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle
endef
define Package/hysteria/description
Hysteria is a feature-packed network utility optimized for networks
of poor quality (e.g. satellite connections, congested public Wi-Fi,
connecting from China to servers abroad) powered by a custom version
of QUIC protocol.
endef
define Package/hysteria/install
$(call GoPackage/Package/Install/Bin,$(PKG_INSTALL_DIR))
$(INSTALL_DIR) $(1)/usr/bin/
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/cmd $(1)/usr/bin/hysteria
endef
$(eval $(call GoBinPackage,hysteria))
$(eval $(call BuildPackage,hysteria))

176
luci-app-passwall/Makefile Normal file
View File

@ -0,0 +1,176 @@
# Copyright (C) 2018-2020 L-WRT Team
# Copyright (C) 2021-2022 xiaorouji
#
# This is free software, licensed under the GNU General Public License v3.
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall
PKG_VERSION:=4.53
PKG_RELEASE:=1
PKG_CONFIG_DEPENDS:= \
CONFIG_PACKAGE_$(PKG_NAME)_Transparent_Proxy \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Brook \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_ChinaDNS_NG \
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_PDNSD \
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_Trojan_GO \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Trojan_Plus \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_V2ray \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Plugin \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Xray \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Xray_Plugin
LUCI_TITLE:=LuCI support for PassWall
LUCI_PKGARCH:=all
LUCI_DEPENDS:=+coreutils +coreutils-base64 +coreutils-nohup +curl \
+dns2socks +ip-full +libuci-lua +lua +luci-compat +luci-lib-jsonc \
+microsocks +resolveip +tcping +unzip \
+PACKAGE_$(PKG_NAME)_INCLUDE_Brook:brook \
+PACKAGE_$(PKG_NAME)_INCLUDE_ChinaDNS_NG:chinadns-ng \
+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_PDNSD:pdnsd-alt \
+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_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_Trojan_GO:trojan-go \
+PACKAGE_$(PKG_NAME)_INCLUDE_Trojan_Plus:trojan-plus \
+PACKAGE_$(PKG_NAME)_INCLUDE_V2ray:v2ray-core \
+PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Plugin:v2ray-plugin \
+PACKAGE_$(PKG_NAME)_INCLUDE_Xray:xray-core \
+PACKAGE_$(PKG_NAME)_INCLUDE_Xray_Plugin:xray-plugin
define Package/$(PKG_NAME)/config
menu "Configuration"
config PACKAGE_$(PKG_NAME)_Transparent_Proxy
bool "Transparent Proxy"
select PACKAGE_dnsmasq-full
select PACKAGE_ipset
select PACKAGE_ipt2socks
select PACKAGE_iptables
select PACKAGE_iptables-legacy
select PACKAGE_iptables-mod-iprange
select PACKAGE_iptables-mod-socket
select PACKAGE_iptables-mod-tproxy
select PACKAGE_kmod-ipt-nat
default y
config PACKAGE_$(PKG_NAME)_INCLUDE_Brook
bool "Include Brook"
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_ChinaDNS_NG
bool "Include ChinaDNS-NG"
default y
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_PDNSD
bool "Include PDNSD"
default y
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 y if aarch64||arm||i386||x86_64
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_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_Trojan_GO
bool "Include Trojan-GO"
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_Trojan_Plus
bool "Include Trojan-Plus"
default y
config PACKAGE_$(PKG_NAME)_INCLUDE_V2ray
bool "Include V2ray"
default y if aarch64||arm||i386||x86_64
config PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Plugin
bool "Include V2ray-Plugin (Shadowsocks Plugin)"
default y if aarch64||arm||i386||x86_64
config PACKAGE_$(PKG_NAME)_INCLUDE_Xray
bool "Include Xray"
default y if aarch64||arm||i386||x86_64
config PACKAGE_$(PKG_NAME)_INCLUDE_Xray_Plugin
bool "Include Xray-Plugin (Shadowsocks Plugin)"
default n
endmenu
endef
define Package/$(PKG_NAME)/conffiles
/etc/config/passwall
/etc/config/passwall_server
/usr/share/passwall/rules/direct_host
/usr/share/passwall/rules/direct_ip
/usr/share/passwall/rules/proxy_host
/usr/share/passwall/rules/proxy_ip
/usr/share/passwall/rules/block_host
/usr/share/passwall/rules/block_ip
/usr/share/passwall/rules/lanlist_ipv4
/usr/share/passwall/rules/lanlist_ipv6
/usr/share/passwall/rules/domains_excluded
endef
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -0,0 +1,491 @@
-- Copyright (C) 2018-2020 L-WRT Team
-- Copyright (C) 2021-2022 xiaorouji
module("luci.controller.passwall", package.seeall)
local api = require "luci.model.cbi.passwall.api.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"
local brook = require("luci.model.cbi." .. appname ..".api.brook")
local v2ray = require("luci.model.cbi." .. appname ..".api.v2ray")
local xray = require("luci.model.cbi." .. appname ..".api.xray")
local trojan_go = require("luci.model.cbi." .. appname ..".api.trojan_go")
local hysteria = require("luci.model.cbi." .. appname ..".api.hysteria")
function index()
appname = require "luci.model.cbi.passwall.api.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/passwall") then return end
if nixio.fs.access("/etc/config/passwall_show") then
e = entry({"admin", "services", appname}, alias("admin", "services", appname, "settings"), _("Pass Wall"), -1)
e.dependent = true
e.acl_depends = { "luci-app-passwall" }
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, "rule_list"}, cbi(appname .. "/client/rule_list"), _("Rule List"), 97).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
entry({"admin", "services", appname, "brook_check"}, call("brook_check")).leaf = true
entry({"admin", "services", appname, "brook_update"}, call("brook_update")).leaf = true
entry({"admin", "services", appname, "v2ray_check"}, call("v2ray_check")).leaf = true
entry({"admin", "services", appname, "v2ray_update"}, call("v2ray_update")).leaf = true
entry({"admin", "services", appname, "xray_check"}, call("xray_check")).leaf = true
entry({"admin", "services", appname, "xray_update"}, call("xray_update")).leaf = true
entry({"admin", "services", appname, "trojan_go_check"}, call("trojan_go_check")).leaf = true
entry({"admin", "services", appname, "trojan_go_update"}, call("trojan_go_update")).leaf = true
entry({"admin", "services", appname, "hysteria_check"}, call("hysteria_check")).leaf = true
entry({"admin", "services", appname, "hysteria_update"}, call("hysteria_update")).leaf = true
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/passwall stop')
luci.sys.call('[ -f "/usr/share/passwall/0_default_config" ] && cp -f /usr/share/passwall/0_default_config /etc/config/passwall')
luci.http.redirect(api.url())
end
function show_menu()
luci.sys.call("touch /etc/config/passwall_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/passwall_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/passwall/subscribe.lua add log")
end
function autoswitch_add_node()
local key = luci.http.formvalue("key")
if key and key ~= "" then
for k, e in ipairs(api.get_valid_nodes()) do
if e.node_type == "normal" and e["remark"]:find(key) then
luci.sys.call(string.format("uci -q del_list passwall.@auto_switch[0].tcp_node='%s' && uci -q add_list passwall.@auto_switch[0].tcp_node='%s'", e.id, e.id))
end
end
end
luci.http.redirect(api.url("auto_switch"))
end
function autoswitch_remove_node()
local key = luci.http.formvalue("key")
if key and key ~= "" then
for k, e in ipairs(ucic:get(appname, "@auto_switch[0]", "tcp_node") or {}) do
if e and (ucic:get(appname, e, "remarks") or ""):find(key) then
luci.sys.call(string.format("uci -q del_list passwall.@auto_switch[0].tcp_node='%s'", e))
end
end
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/passwall/id/TCP")
if data then
e["TCP"] = util.trim(data)
end
local data, code, msg = nixio.fs.readfile("/tmp/etc/passwall/id/UDP")
if data then
e["UDP"] = util.trim(data)
end
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function get_redir_log()
local proto = luci.http.formvalue("proto")
proto = proto:upper()
if proto == "UDP" and (ucic:get(appname, "@global[0]", "udp_node") or "nil") == "tcp" and not nixio.fs.access("/tmp/etc/passwall/" .. proto .. ".log") then
proto = "TCP"
end
if nixio.fs.access("/tmp/etc/passwall/" .. proto .. ".log") then
local content = luci.sys.exec("cat /tmp/etc/passwall/" .. proto .. ".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/passwall.log ] && sed '1!G;h;$!d' /tmp/log/passwall.log > /tmp/log/passwall_show.log")
luci.http.write(luci.sys.exec("[ -f '/tmp/log/passwall.log' ] && cat /tmp/log/passwall.log"))
end
function clear_log()
luci.sys.call("echo '' > /tmp/log/passwall.log")
end
function status()
-- local dns_mode = ucic:get(appname, "@global[0]", "dns_mode")
local e = {}
e.dns_mode_status = luci.sys.call("netstat -apn | grep ':7913 ' >/dev/null") == 0
e.haproxy_status = luci.sys.call(string.format("/bin/top -bn1 | grep -v grep | grep '%s/bin/' | grep haproxy >/dev/null", appname)) == 0
e["tcp_node_status"] = luci.sys.call(string.format("/bin/top -bn1 | grep -v -E 'grep|acl/|acl_' | grep '%s/bin/' | grep -i 'TCP' >/dev/null", appname)) == 0
if (ucic:get(appname, "@global[0]", "udp_node") or "nil") == "tcp" then
e["udp_node_status"] = e["tcp_node_status"]
else
e["udp_node_status"] = luci.sys.call(string.format("/bin/top -bn1 | grep -v -E 'grep|acl/|acl_' | grep '%s/bin/' | grep -i 'UDP' >/dev/null", appname)) == 0
end
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function haproxy_status()
local e = luci.sys.call(string.format("/bin/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("/bin/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("/bin/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 -skL -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/passwall/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 protocol = luci.http.formvalue("protocol")
local section = luci.http.formvalue("section")
ucic:set(appname, "@global[0]", protocol .. "_node", section)
ucic:commit(appname)
luci.sys.call("/etc/init.d/passwall 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_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]', "tcp_node", "nil")
ucic:set(appname, '@global[0]', "udp_node", "nil")
ucic:set_list(appname, "@auto_switch[0]", "tcp_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"], "tcp_node", "default")
ucic:set(appname, t[".name"], "udp_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_tcp_node_list = ucic:get(appname, "@auto_switch[0]", "tcp_node") or {}
string.gsub(ids, '[^' .. "," .. ']+', function(w)
for k, v in ipairs(auto_switch_tcp_node_list) do
if v == w then
luci.sys.call(string.format("uci -q del_list passwall.@auto_switch[0].tcp_node='%s'", w))
end
end
if (ucic:get(appname, "@global[0]", "tcp_node") or "nil") == w then
ucic:set(appname, '@global[0]', "tcp_node", "nil")
end
if (ucic:get(appname, "@global[0]", "udp_node") or "nil") == w then
ucic:set(appname, '@global[0]', "udp_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["tcp_node"] == w then
ucic:set(appname, t[".name"], "tcp_node", "default")
end
if t["udp_node"] == w then
ucic:set(appname, t[".name"], "udp_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/passwall/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("/bin/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/passwall_server/" .. id .. ".log") then
local content = luci.sys.exec("cat /tmp/etc/passwall_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/passwall_server.log' ] && cat /tmp/log/passwall_server.log"))
end
function server_clear_log()
luci.sys.call("echo '' > /tmp/log/passwall_server.log")
end
function brook_check()
local json = brook.to_check("")
http_write_json(json)
end
function brook_update()
local json = nil
local task = http.formvalue("task")
if task == "move" then
json = brook.to_move(http.formvalue("file"))
else
json = brook.to_download(http.formvalue("url"), http.formvalue("size"))
end
http_write_json(json)
end
function v2ray_check()
local json = v2ray.to_check("")
http_write_json(json)
end
function v2ray_update()
local json = nil
local task = http.formvalue("task")
if task == "extract" then
json = v2ray.to_extract(http.formvalue("file"), http.formvalue("subfix"))
elseif task == "move" then
json = v2ray.to_move(http.formvalue("file"))
else
json = v2ray.to_download(http.formvalue("url"), http.formvalue("size"))
end
http_write_json(json)
end
function xray_check()
local json = xray.to_check("")
http_write_json(json)
end
function xray_update()
local json = nil
local task = http.formvalue("task")
if task == "extract" then
json = xray.to_extract(http.formvalue("file"), http.formvalue("subfix"))
elseif task == "move" then
json = xray.to_move(http.formvalue("file"))
else
json = xray.to_download(http.formvalue("url"), http.formvalue("size"))
end
http_write_json(json)
end
function trojan_go_check()
local json = trojan_go.to_check("")
http_write_json(json)
end
function trojan_go_update()
local json = nil
local task = http.formvalue("task")
if task == "extract" then
json = trojan_go.to_extract(http.formvalue("file"), http.formvalue("subfix"))
elseif task == "move" then
json = trojan_go.to_move(http.formvalue("file"))
else
json = trojan_go.to_download(http.formvalue("url"), http.formvalue("size"))
end
http_write_json(json)
end
function hysteria_check()
local json = hysteria.to_check("")
http_write_json(json)
end
function hysteria_update()
local json = nil
local task = http.formvalue("task")
if task == "move" then
json = hysteria.to_move(http.formvalue("file"))
else
json = hysteria.to_download(http.formvalue("url"), http.formvalue("size"))
end
http_write_json(json)
end

View File

@ -0,0 +1,617 @@
module("luci.model.cbi.passwall.api.api", package.seeall)
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 = "passwall"
curl = "/usr/bin/curl"
curl_args = {"-skfL", "--connect-timeout 3", "--retry 3", "-m 60"}
command_timeout = 300
LEDE_BOARD = nil
DISTRIB_TARGET = nil
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 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 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") 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_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") 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_full_node_remarks(n)
local remarks = ""
if n then
if n.protocol and (n.protocol == "_balancing" or n.protocol == "_shunt") 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] %s:%s" % {type2, n.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 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 "%s" "%s"' % {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
function get_bin_version_cache(file, cmd)
sys.call("mkdir -p /tmp/etc/passwall_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/passwall_tmp/" .. md5) then
return sys.exec("echo -n $(cat /tmp/etc/passwall_tmp/%s)" % md5)
else
local version = sys.exec(string.format("echo -n $(%s %s)", file, cmd))
sys.call("echo '" .. version .. "' > " .. "/tmp/etc/passwall_tmp/" .. md5)
return version
end
end
return ""
end
function get_v2ray_path()
local path = uci_get_type("global_app", "v2ray_file")
return path
end
function get_v2ray_version(file)
if file == nil then file = get_v2ray_path() end
local cmd = "-version | awk '{print $2}' | sed -n 1P"
return get_bin_version_cache(file, cmd)
end
function get_xray_path()
local path = uci_get_type("global_app", "xray_file")
return path
end
function get_xray_version(file)
if file == nil then file = get_xray_path() end
local cmd = "-version | awk '{print $2}' | sed -n 1P"
return get_bin_version_cache(file, cmd)
end
function get_trojan_go_path()
local path = uci_get_type("global_app", "trojan_go_file")
return path
end
function get_trojan_go_version(file)
if file == nil then file = get_trojan_go_path() end
local cmd = "-version | awk '{print $2}' | sed -n 1P"
return get_bin_version_cache(file, cmd)
end
function get_brook_path()
local path = uci_get_type("global_app", "brook_file")
return path
end
function get_brook_version(file)
if file == nil then file = get_brook_path() end
local cmd = "-v | awk '{print $3}'"
return get_bin_version_cache(file, cmd)
end
function get_hysteria_path()
local path = uci_get_type("global_app", "hysteria_file")
return path
end
function get_hysteria_version(file)
if file == nil then file = get_hysteria_path() end
local cmd = "-v | awk '{print $3}'"
return get_bin_version_cache(file, cmd)
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 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 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
function auto_get_arch()
local arch = nixio.uname().machine or ""
if fs.access("/usr/lib/os-release") then
LEDE_BOARD = sys.exec("echo -n $(grep 'LEDE_BOARD' /usr/lib/os-release | awk -F '[\\042\\047]' '{print $2}')")
end
if fs.access("/etc/openwrt_release") then
DISTRIB_TARGET = sys.exec("echo -n $(grep 'DISTRIB_TARGET' /etc/openwrt_release | awk -F '[\\042\\047]' '{print $2}')")
end
if arch == "mips" then
if LEDE_BOARD and LEDE_BOARD ~= "" then
if string.match(LEDE_BOARD, "ramips") == "ramips" then
arch = "ramips"
else
arch = sys.exec("echo '" .. LEDE_BOARD .. "' | grep -oE 'ramips|ar71xx'")
end
elseif DISTRIB_TARGET and DISTRIB_TARGET ~= "" then
if string.match(DISTRIB_TARGET, "ramips") == "ramips" then
arch = "ramips"
else
arch = sys.exec("echo '" .. DISTRIB_TARGET .. "' | grep -oE 'ramips|ar71xx'")
end
end
end
return util.trim(arch)
end
function get_file_info(arch)
local file_tree = ""
local sub_version = ""
if arch == "x86_64" then
file_tree = "amd64"
elseif arch == "aarch64" then
file_tree = "arm64"
elseif arch == "ramips" then
file_tree = "mipsle"
elseif arch == "ar71xx" then
file_tree = "mips"
elseif arch:match("^i[%d]86$") then
file_tree = "386"
elseif arch:match("^armv[5-8]") then
file_tree = "arm"
sub_version = arch:match("[5-8]")
if LEDE_BOARD and string.match(LEDE_BOARD, "bcm53xx") == "bcm53xx" then
sub_version = "5"
elseif DISTRIB_TARGET and string.match(DISTRIB_TARGET, "bcm53xx") ==
"bcm53xx" then
sub_version = "5"
end
sub_version = "5"
end
return file_tree, sub_version
end
function get_api_json(url)
local jsonc = require "luci.jsonc"
local json_content = luci.sys.exec(curl .. " " .. _unpack(curl_args) .. " " .. url)
if json_content == "" then return {} end
return jsonc.parse(json_content) or {}
end
function common_to_check(api_url, local_version, match_file_name)
local json = get_api_json(api_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

View File

@ -0,0 +1,134 @@
module("luci.model.cbi.passwall.api.brook", package.seeall)
local api = require "luci.model.cbi.passwall.api.api"
local fs = api.fs
local sys = api.sys
local util = api.util
local i18n = api.i18n
local pre_release_url = "https://api.github.com/repos/txthinking/brook/releases?per_page=1"
local release_url = "https://api.github.com/repos/txthinking/brook/releases/latest"
local api_url = release_url
local app_path = api.get_brook_path() or ""
function check_path()
if app_path == "" then
return {
code = 1,
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", "Brook")
}
end
return {
code = 0
}
end
function to_check(arch)
local result = check_path()
if result.code ~= 0 then
return result
end
if not arch or arch == "" then arch = api.auto_get_arch() end
local file_tree, sub_version = api.get_file_info(arch)
if file_tree == "" then
return {
code = 1,
error = i18n.translate("Can't determine ARCH, or ARCH not supported.")
}
end
return api.common_to_check(api_url, api.get_brook_version(), "linux_" .. file_tree .. sub_version)
end
function to_download(url, size)
local result = check_path()
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/brook_download.*")
local tmp_file = util.trim(util.exec("mktemp -u -t brook_download.XXXXXX"))
if size then
local kb1 = api.get_free_space("/tmp")
if tonumber(size) > tonumber(kb1) then
return {code = 1, error = i18n.translatef("%s not enough space.", "/tmp")}
end
end
result = api.exec(api.curl, {api._unpack(api.curl_args), "-o", tmp_file, url}, nil, api.command_timeout) == 0
if not result then
api.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}
end
function to_move(file)
local result = check_path()
if result.code ~= 0 then
return result
end
if not file or file == "" or not fs.access(file) then
sys.call("/bin/rm -rf /tmp/brook_download.*")
return {code = 1, error = i18n.translate("Client file is required.")}
end
local new_version = api.get_brook_version(file)
if new_version == "" then
sys.call("/bin/rm -rf /tmp/brook_download.*")
return {
code = 1,
error = i18n.translate("The client file is not suitable for current device.")
}
end
local flag = sys.call('pgrep -af "passwall/.*brook" >/dev/null')
if flag == 0 then
sys.call("/etc/init.d/passwall stop")
end
local old_app_size = 0
if fs.access(app_path) then
old_app_size = api.get_file_space(app_path)
end
local new_app_size = api.get_file_space(file)
local final_dir = api.get_final_dir(app_path)
local final_dir_free_size = api.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("/bin/rm -rf /tmp/brook_download.*")
return {code = 1, error = i18n.translatef("%s not enough space.", final_dir)}
end
end
result = api.exec("/bin/mv", {"-f", file, app_path}, nil, api.command_timeout) == 0
sys.call("/bin/rm -rf /tmp/brook_download.*")
if flag == 0 then
sys.call("/etc/init.d/passwall 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

View File

@ -0,0 +1,70 @@
local api = require "luci.model.cbi.passwall.api.api"
local uci = api.uci
local jsonc = api.jsonc
local var = api.get_args(arg)
local node_id = var["-node"]
if not node_id then
print("-node 不能为空")
return
end
local node = uci:get_all("passwall", node_id)
local local_tcp_redir_port = var["-local_tcp_redir_port"]
local local_udp_redir_port = var["-local_udp_redir_port"]
local local_socks_address = var["-local_socks_address"] or "0.0.0.0"
local local_socks_port = var["-local_socks_port"]
local local_socks_username = var["-local_socks_username"]
local local_socks_password = var["-local_socks_password"]
local local_http_address = var["-local_http_address"] or "0.0.0.0"
local local_http_port = var["-local_http_port"]
local local_http_username = var["-local_http_username"]
local local_http_password = var["-local_http_password"]
local 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 = {
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,
disable_mtu_discovery = (node.hysteria_disable_mtu_discovery) 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,
tproxy_tcp = (local_tcp_redir_port) and {
listen = "0.0.0.0:" .. local_tcp_redir_port,
timeout = 300
} or nil,
tproxy_udp = (local_udp_redir_port) and {
listen = "0.0.0.0:" .. local_udp_redir_port,
timeout = 60
} or nil
}
print(jsonc.stringify(config, 1))

View File

@ -0,0 +1,28 @@
local api = require "luci.model.cbi.passwall.api.api"
local uci = api.uci
local jsonc = api.jsonc
local var = api.get_args(arg)
local node_id = var["-node"]
if not node_id then
print("-node 不能为空")
return
end
local node = uci:get_all("passwall", 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
}
print(jsonc.stringify(config, 1))

View File

@ -0,0 +1,108 @@
local api = require "luci.model.cbi.passwall.api.api"
local uci = api.uci
local jsonc = api.jsonc
local var = api.get_args(arg)
local node_id = var["-node"]
if not node_id then
print("-node 不能为空")
return
end
local node = uci:get_all("passwall", 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"]
local local_tcp_redir_port = var["-local_tcp_redir_port"]
local local_tcp_redir_address = var["-local_tcp_redir_address"] or "0.0.0.0"
local local_udp_redir_port = var["-local_udp_redir_port"]
local local_udp_redir_address = var["-local_udp_redir_address"] or "0.0.0.0"
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,
tcp_tproxy = var["-tcp_tproxy"] and true or nil
}
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
if local_tcp_redir_address and local_tcp_redir_port then
table.insert(config.locals, {
protocol = "redir",
mode = "tcp_only",
tcp_redir = var["-tcp_tproxy"] and "tproxy" or nil,
local_address = local_tcp_redir_address,
local_port = tonumber(local_tcp_redir_port)
})
end
if local_udp_redir_address and local_udp_redir_port then
table.insert(config.locals, {
protocol = "redir",
mode = "udp_only",
local_address = local_udp_redir_address,
local_port = tonumber(local_udp_redir_port)
})
end
end
print(jsonc.stringify(config, 1))

View File

@ -0,0 +1,86 @@
local api = require "luci.model.cbi.passwall.api.api"
local uci = api.uci
local json = api.jsonc
local var = api.get_args(arg)
local node_id = var["-node"]
if not node_id then
print("-node 不能为空")
return
end
local node = uci:get_all("passwall", 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
local loglevel = var["-loglevel"] or 2
local cipher = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:AES128-SHA:AES256-SHA:DES-CBC3-SHA"
local cipher13 = "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384"
if api.is_ipv6(server_host) then
server_host = api.get_ipv6_only(server_host)
end
local server = server_host
local trojan = {
run_type = run_type,
local_addr = local_addr,
local_port = tonumber(local_port),
remote_addr = server,
remote_port = tonumber(server_port),
password = {node.password},
log_level = tonumber(loglevel),
ssl = {
verify = (node.tls_allowInsecure ~= "1") and true or false,
verify_hostname = true,
cert = nil,
cipher = cipher,
cipher_tls13 = cipher13,
sni = node.tls_serverName or server,
alpn = {"h2", "http/1.1"},
reuse_session = true,
session_ticket = (node.tls_sessionTicket and node.tls_sessionTicket == "1") and true or false,
curves = ""
},
udp_timeout = 60,
tcp = {
use_tproxy = (node.type == "Trojan-Plus" and var["-use_tproxy"]) and true or nil,
no_delay = true,
keep_alive = true,
reuse_port = true,
fast_open = (node.tcp_fast_open == "true") and true or false,
fast_open_qlen = 20
}
}
if node.type == "Trojan-Go" then
trojan.ssl.cipher = nil
trojan.ssl.cipher_tls13 = nil
trojan.ssl.fingerprint = (node.fingerprint ~= "disable") and node.fingerprint or ""
trojan.ssl.alpn = (node.trojan_transport == 'ws') and {} or {"h2", "http/1.1"}
if node.tls ~= "1" and node.trojan_transport == "original" then trojan.ssl = nil end
trojan.transport_plugin = ((not node.tls or node.tls ~= "1") and node.trojan_transport == "original") and {
enabled = node.plugin_type ~= nil,
type = node.plugin_type or "plaintext",
command = node.plugin_type ~= "plaintext" and node.plugin_cmd or nil,
option = node.plugin_type ~= "plaintext" and node.plugin_option or nil,
arg = node.plugin_type ~= "plaintext" and { node.plugin_arg } or nil,
env = {}
} or nil
trojan.websocket = (node.trojan_transport == 'ws') and {
enabled = true,
path = node.ws_path or "/",
host = node.ws_host or (node.tls_serverName or server)
} or nil
trojan.shadowsocks = (node.ss_aead == "1") and {
enabled = true,
method = node.ss_aead_method or "aes_128_gcm",
password = node.ss_aead_pwd or ""
} or nil
trojan.mux = (node.smux == "1") and {
enabled = true,
concurrency = tonumber(node.mux_concurrency),
idle_timeout = tonumber(node.smux_idle_timeout)
} or nil
end
print(json.stringify(trojan, 1))

View File

@ -0,0 +1,709 @@
module("luci.model.cbi.passwall.api.gen_v2ray", package.seeall)
local api = require "luci.model.cbi.passwall.api.api"
local var = api.get_args(arg)
local flag = var["-flag"]
local node_id = var["-node"]
local tcp_proxy_way = var["-tcp_proxy_way"] or "redirect"
local tcp_redir_port = var["-tcp_redir_port"]
local udp_redir_port = var["-udp_redir_port"]
local sniffing = var["-sniffing"]
local route_only = var["-route_only"]
local local_socks_address = var["-local_socks_address"] or "0.0.0.0"
local local_socks_port = var["-local_socks_port"]
local local_socks_username = var["-local_socks_username"]
local local_socks_password = var["-local_socks_password"]
local local_http_address = var["-local_http_address"] or "0.0.0.0"
local local_http_port = var["-local_http_port"]
local local_http_username = var["-local_http_username"]
local local_http_password = var["-local_http_password"]
local dns_listen_port = var["-dns_listen_port"]
local dns_server = var["-dns_server"]
local dns_tcp_server = var["-dns_tcp_server"]
local dns_cache = var["-dns_cache"]
local doh_url = var["-doh_url"]
local doh_host = var["-doh_host"]
local dns_client_ip = var["-dns_client_ip"]
local dns_query_strategy = var["-dns_query_strategy"]
local dns_socks_address = var["-dns_socks_address"]
local dns_socks_port = var["-dns_socks_port"]
local dns_fakedns = var["-dns_fakedns"]
local loglevel = var["-loglevel"] or "warning"
local new_port
local uci = api.uci
local sys = api.sys
local jsonc = api.jsonc
local appname = api.appname
local fs = api.fs
local dns = nil
local fakedns = nil
local inbounds = {}
local outbounds = {}
local routing = nil
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 function get_domain_excluded()
local path = string.format("/usr/share/%s/rules/domains_excluded", appname)
local content = fs.readfile(path)
if not content then return nil end
local hosts = {}
string.gsub(content, '[^' .. "\n" .. ']+', function(w)
local s = w:gsub("^%s*(.-)%s*$", "%1") -- Trim
if s == "" then return end
if s:find("#") and s:find("#") == 1 then return end
if not s:find("#") or s:find("#") ~= 1 then table.insert(hosts, s) end
end)
if #hosts == 0 then hosts = nil end
return hosts
end
function gen_outbound(node, tag, proxy_table)
local proxy = 0
local proxy_tag = "nil"
if proxy_table ~= nil and type(proxy_table) == "table" then
proxy = proxy_table.proxy or 0
proxy_tag = proxy_table.tag or "nil"
end
local result = nil
if node and node ~= "nil" then
local node_id = node[".name"]
if tag == nil then
tag = node_id
end
if node.type == "V2ray" or node.type == "Xray" then
proxy = 0
if proxy_tag ~= "nil" then
node.proxySettings = {
tag = proxy_tag,
transportLayer = true
}
end
end
if node.type ~= "V2ray" and node.type ~= "Xray" then
if node.type == "Socks" then
node.protocol = "socks"
node.transport = "tcp"
else
local relay_port = node.port
new_port = get_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 relay_port=%s",
new_port, --flag
node_id, --node
"127.0.0.1", --bind
new_port, --socks port
string.format("%s_%s_%s_%s.json", flag, tag, node_id, new_port), --config file
(proxy == 1 and proxy_tag ~= "nil" and relay_port) and tostring(relay_port) or "" --relay port
)
)
)
node = {}
node.protocol = "socks"
node.transport = "tcp"
node.address = "127.0.0.1"
node.port = new_port
end
node.stream_security = "none"
else
if node.tls and node.tls == "1" then
node.stream_security = "tls"
if node.type == "Xray" and node.xtls and node.xtls == "1" then
node.stream_security = "xtls"
end
end
end
result = {
_flag_tag = node_id,
_flag_proxy = proxy,
_flag_proxy_tag = proxy_tag,
tag = tag,
proxySettings = node.proxySettings or nil,
protocol = node.protocol,
mux = (node.stream_security ~= "xtls") and {
enabled = (node.mux == "1") and true or false,
concurrency = (node.mux_concurrency) and tonumber(node.mux_concurrency) or 8
} or nil,
-- 底层传输配置
streamSettings = (node.protocol == "vmess" or node.protocol == "vless" or node.protocol == "socks" or node.protocol == "shadowsocks" or node.protocol == "trojan") and {
network = node.transport,
security = node.stream_security,
xtlsSettings = (node.stream_security == "xtls") and {
serverName = node.tls_serverName,
allowInsecure = (node.tls_allowInsecure == "1") and true or false
} or nil,
tlsSettings = (node.stream_security == "tls") and {
serverName = node.tls_serverName,
allowInsecure = (node.tls_allowInsecure == "1") and true or false,
fingerprint = (node.type == "Xray" and node.fingerprint and node.fingerprint ~= "disable") and node.fingerprint or nil
} or nil,
tcpSettings = (node.transport == "tcp" and node.protocol ~= "socks") and {
header = {
type = node.tcp_guise or "none",
request = (node.tcp_guise == "http") and {
path = node.tcp_guise_http_path or {"/"},
headers = {
Host = node.tcp_guise_http_host or {}
}
} or nil
}
} or nil,
kcpSettings = (node.transport == "mkcp") and {
mtu = tonumber(node.mkcp_mtu),
tti = tonumber(node.mkcp_tti),
uplinkCapacity = tonumber(node.mkcp_uplinkCapacity),
downlinkCapacity = tonumber(node.mkcp_downlinkCapacity),
congestion = (node.mkcp_congestion == "1") and true or false,
readBufferSize = tonumber(node.mkcp_readBufferSize),
writeBufferSize = tonumber(node.mkcp_writeBufferSize),
seed = (node.mkcp_seed and node.mkcp_seed ~= "") and node.mkcp_seed or nil,
header = {type = node.mkcp_guise}
} or nil,
wsSettings = (node.transport == "ws") and {
path = node.ws_path or "",
headers = (node.ws_host ~= nil) and
{Host = node.ws_host} or nil,
maxEarlyData = tonumber(node.ws_maxEarlyData) or nil
} or nil,
httpSettings = (node.transport == "h2") and {
path = node.h2_path,
host = node.h2_host,
read_idle_timeout = tonumber(node.h2_read_idle_timeout) or nil,
health_check_timeout = tonumber(node.h2_health_check_timeout) or nil
} or nil,
dsSettings = (node.transport == "ds") and
{path = node.ds_path} or nil,
quicSettings = (node.transport == "quic") and {
security = node.quic_security,
key = node.quic_key,
header = {type = node.quic_guise}
} or nil,
grpcSettings = (node.transport == "grpc") and {
serviceName = node.grpc_serviceName,
multiMode = (node.grpc_mode == "multi") and true or nil,
idle_timeout = tonumber(node.grpc_idle_timeout) or nil,
health_check_timeout = tonumber(node.grpc_health_check_timeout) or nil,
permit_without_stream = (node.grpc_permit_without_stream == "1") and true or nil,
initial_windows_size = tonumber(node.grpc_initial_windows_size) or nil
} or nil
} or nil,
settings = {
vnext = (node.protocol == "vmess" or node.protocol == "vless") and {
{
address = node.address,
port = tonumber(node.port),
users = {
{
id = node.uuid,
level = 0,
security = (node.protocol == "vmess") and node.security or nil,
encryption = node.encryption or "none",
flow = node.flow or nil
}
}
}
} or nil,
servers = (node.protocol == "socks" or node.protocol == "http" or node.protocol == "shadowsocks" or node.protocol == "trojan") and {
{
address = node.address,
port = tonumber(node.port),
method = node.method or nil,
flow = node.flow or nil,
ivCheck = (node.protocol == "shadowsocks") and node.iv_check == "1" or nil,
password = node.password or "",
users = (node.username and node.password) and {
{
user = node.username,
pass = node.password
}
} or nil
}
} or nil
}
}
local alpn = {}
if node.alpn and node.alpn ~= "default" then
string.gsub(node.alpn, '[^' .. "," .. ']+', function(w)
table.insert(alpn, w)
end)
end
if alpn and #alpn > 0 then
if result.streamSettings.tlsSettings then
result.streamSettings.tlsSettings.alpn = alpn
end
if result.streamSettings.xtlsSettings then
result.streamSettings.xtlsSettings.alpn = alpn
end
end
end
return result
end
if node_id then
local node = uci:get_all(appname, node_id)
if local_socks_port then
local inbound = {
listen = local_socks_address,
port = tonumber(local_socks_port),
protocol = "socks",
settings = {auth = "noauth", udp = true},
sniffing = {enabled = true, destOverride = {"http", "tls"}}
}
if local_socks_username and local_socks_password and local_socks_username ~= "" and local_socks_password ~= "" then
inbound.settings.auth = "password"
inbound.settings.accounts = {
{
user = local_socks_username,
pass = local_socks_password
}
}
end
table.insert(inbounds, inbound)
end
if local_http_port then
local inbound = {
listen = local_http_address,
port = tonumber(local_http_port),
protocol = "http",
settings = {allowTransparent = false}
}
if local_http_username and local_http_password and local_http_username ~= "" and local_http_password ~= "" then
inbound.settings.accounts = {
{
user = local_http_username,
pass = local_http_password
}
}
end
table.insert(inbounds, inbound)
end
if tcp_redir_port then
table.insert(inbounds, {
port = tonumber(tcp_redir_port),
protocol = "dokodemo-door",
settings = {network = "tcp", followRedirect = true},
streamSettings = {sockopt = {tproxy = tcp_proxy_way}},
sniffing = {enabled = sniffing and true or false, destOverride = {"http", "tls", (dns_fakedns) and "fakedns"}, metadataOnly = false, routeOnly = route_only and true or nil, domainsExcluded = (sniffing and not route_only) and get_domain_excluded() or nil}
})
end
if udp_redir_port then
table.insert(inbounds, {
port = tonumber(udp_redir_port),
protocol = "dokodemo-door",
settings = {network = "udp", followRedirect = true},
streamSettings = {sockopt = {tproxy = "tproxy"}},
sniffing = {enabled = sniffing and true or false, destOverride = {"http", "tls", (dns_fakedns) and "fakedns"}, metadataOnly = false, routeOnly = route_only and true or nil, domainsExcluded = (sniffing and not route_only) and get_domain_excluded() or nil}
})
end
local up_trust_doh = uci:get(appname, "@global[0]", "up_trust_doh")
if up_trust_doh then
local t = {}
string.gsub(up_trust_doh, '[^' .. "," .. ']+', function (w)
table.insert(t, w)
end)
if #t > 1 then
local host = sys.exec("echo -n $(echo " .. t[1] .. " | sed 's/https:\\/\\///g' | awk -F ':' '{print $1}' | awk -F '/' '{print $1}')")
dns = {
hosts = {
[host] = t[2]
}
}
end
end
if node.protocol == "_shunt" then
table.insert(outbounds, {
protocol = "freedom",
tag = "direct",
settings = {
domainStrategy = "UseIPv4"
},
streamSettings = {
sockopt = {
mark = 255
}
}
})
table.insert(outbounds, {
protocol = "blackhole",
tag = "blackhole"
})
local rules = {}
local default_node_id = node.default_node or "_direct"
local default_outboundTag
if default_node_id == "_direct" then
default_outboundTag = "direct"
elseif default_node_id == "_blackhole" then
default_outboundTag = "blackhole"
else
local default_node = uci:get_all(appname, default_node_id)
local main_node_id = node.main_node or "nil"
local proxy = 0
local proxy_tag
if main_node_id ~= "nil" then
local main_node = uci:get_all(appname, main_node_id)
if main_node and api.is_normal_node(main_node) and main_node_id ~= default_node_id then
local main_node_outbound = gen_outbound(main_node, "main")
if main_node_outbound then
table.insert(outbounds, main_node_outbound)
proxy = 1
proxy_tag = "main"
if default_node.type ~= "V2ray" and default_node.type ~= "Xray" then
proxy_tag = nil
new_port = get_new_port()
table.insert(inbounds, {
tag = "proxy_default",
listen = "127.0.0.1",
port = new_port,
protocol = "dokodemo-door",
settings = {network = "tcp,udp", address = default_node.address, port = tonumber(default_node.port)}
})
if default_node.tls_serverName == nil then
default_node.tls_serverName = default_node.address
end
default_node.address = "127.0.0.1"
default_node.port = new_port
table.insert(rules, 1, {
type = "field",
inboundTag = {"proxy_default"},
outboundTag = "main"
})
end
end
end
end
if default_node and api.is_normal_node(default_node) then
local default_outbound = gen_outbound(default_node, "default", { proxy = proxy, tag = proxy_tag })
if default_outbound then
table.insert(outbounds, default_outbound)
default_outboundTag = "default"
end
end
end
uci:foreach(appname, "shunt_rules", function(e)
local name = e[".name"]
if name and e.remarks then
local _node_id = node[name] or "nil"
local proxy_tag = node[name .. "_proxy_tag"] or "nil"
local outboundTag
if _node_id == "_direct" then
outboundTag = "direct"
elseif _node_id == "_blackhole" then
outboundTag = "blackhole"
elseif _node_id == "_default" then
outboundTag = "default"
else
if _node_id ~= "nil" then
local _node = uci:get_all(appname, _node_id)
if _node and api.is_normal_node(_node) then
local new_outbound
for index, value in ipairs(outbounds) do
if value["_flag_tag"] == _node_id and value["_flag_proxy_tag"] == proxy_tag then
new_outbound = api.clone(value)
break
end
end
if new_outbound then
new_outbound["tag"] = name
table.insert(outbounds, new_outbound)
outboundTag = name
else
if _node.type ~= "V2ray" and _node.type ~= "Xray" then
if proxy_tag ~= "nil" then
new_port = get_new_port()
table.insert(inbounds, {
tag = "proxy_" .. name,
listen = "127.0.0.1",
port = new_port,
protocol = "dokodemo-door",
settings = {network = "tcp,udp", address = _node.address, port = tonumber(_node.port)}
})
if _node.tls_serverName == nil then
_node.tls_serverName = _node.address
end
_node.address = "127.0.0.1"
_node.port = new_port
table.insert(rules, 1, {
type = "field",
inboundTag = {"proxy_" .. name},
outboundTag = proxy_tag
})
end
end
local _outbound = gen_outbound(_node, name, { proxy = (proxy_tag ~= "nil") and 1 or 0, tag = (proxy_tag ~= "nil") and proxy_tag or nil })
if _outbound then
table.insert(outbounds, _outbound)
outboundTag = name
end
end
end
end
end
if outboundTag then
if outboundTag == "default" then
outboundTag = default_outboundTag
end
local protocols = nil
if e["protocol"] and e["protocol"] ~= "" then
protocols = {}
string.gsub(e["protocol"], '[^' .. " " .. ']+', function(w)
table.insert(protocols, w)
end)
end
if e.domain_list then
local _domain = {}
string.gsub(e.domain_list, '[^' .. "\r\n" .. ']+', function(w)
table.insert(_domain, w)
end)
table.insert(rules, {
type = "field",
outboundTag = outboundTag,
domain = _domain,
protocol = protocols
})
end
if e.ip_list then
local _ip = {}
string.gsub(e.ip_list, '[^' .. "\r\n" .. ']+', function(w)
table.insert(_ip, w)
end)
table.insert(rules, {
type = "field",
outboundTag = outboundTag,
ip = _ip,
protocol = protocols
})
end
if not e.domain_list and not e.ip_list and protocols then
table.insert(rules, {
type = "field",
outboundTag = outboundTag,
protocol = protocols
})
end
end
end
end)
if default_outboundTag then
table.insert(rules, {
type = "field",
outboundTag = default_outboundTag,
network = "tcp,udp"
})
end
routing = {
domainStrategy = node.domainStrategy or "AsIs",
domainMatcher = node.domainMatcher or "hybrid",
rules = rules
}
elseif node.protocol == "_balancing" then
if node.balancing_node then
local nodes = node.balancing_node
local length = #nodes
for i = 1, length do
local node = uci:get_all(appname, nodes[i])
local outbound = gen_outbound(node)
if outbound then table.insert(outbounds, outbound) end
end
routing = {
domainStrategy = node.domainStrategy or "AsIs",
domainMatcher = node.domainMatcher or "hybrid",
balancers = {{tag = "balancer", selector = nodes}},
rules = {
{type = "field", network = "tcp,udp", balancerTag = "balancer"}
}
}
end
else
local outbound = gen_outbound(node)
if outbound then table.insert(outbounds, outbound) end
routing = {
domainStrategy = "AsIs",
domainMatcher = "hybrid",
rules = {}
}
end
end
if dns_server or dns_fakedns then
table.insert(outbounds, {
protocol = "dns",
tag = "dns-out"
})
local rules = {}
dns = {
tag = "dns-in1",
disableCache = (dns_cache and dns_cache == "0") and true or false,
servers = {
dns_server
},
clientIp = (dns_client_ip and dns_client_ip ~= "") and dns_client_ip or nil,
queryStrategy = (dns_query_strategy and dns_query_strategy ~= "") and dns_query_strategy or nil
}
if doh_url and doh_host then
dns.hosts = {
[doh_host] = dns_server
}
if not tcp_redir_port and not dns_socks_port then
doh_url = doh_url:gsub("https://", "https+local://")
end
dns.servers = {
doh_url
}
end
if dns_tcp_server then
if not tcp_redir_port and not dns_socks_port then
dns_tcp_server = dns_tcp_server:gsub("tcp://", "tcp+local://")
end
dns.servers = {
dns_tcp_server
}
end
if dns_fakedns then
fakedns = {}
fakedns[#fakedns + 1] = {
ipPool = "198.18.0.0/16",
poolSize = 65535
}
dns_server = "1.1.1.1"
dns.servers = {
"fakedns"
}
end
if dns_listen_port then
table.insert(inbounds, {
listen = "127.0.0.1",
port = tonumber(dns_listen_port),
protocol = "dokodemo-door",
tag = "dns-in",
settings = {
address = dns_server,
port = 53,
network = "tcp,udp"
}
})
end
table.insert(rules, {
type = "field",
inboundTag = {
"dns-in"
},
outboundTag = "dns-out"
})
if dns_socks_address and dns_socks_port then
table.insert(outbounds, 1, {
tag = "out",
protocol = "socks",
streamSettings = {
network = "tcp",
security = "none"
},
settings = {
servers = {
{
address = dns_socks_address,
port = tonumber(dns_socks_port)
}
}
}
})
local outboundTag = "out"
table.insert(rules, {
type = "field",
inboundTag = {
"dns-in1"
},
outboundTag = outboundTag
})
end
if node_id and tcp_redir_port and not dns_fakedns then
local outboundTag = node_id
local node = uci:get_all(appname, node_id)
if node.protocol == "_shunt" then
outboundTag = "default"
end
table.insert(rules, {
type = "field",
inboundTag = {
"dns-in1"
},
outboundTag = outboundTag
})
end
if not routing then
routing = {
domainStrategy = "IPOnDemand",
rules = rules
}
else
for index, value in ipairs(rules) do
table.insert(routing.rules, 1, value)
end
end
end
if inbounds or outbounds then
local config = {
log = {
-- error = string.format("/tmp/etc/%s/%s.log", appname, node[".name"]),
loglevel = loglevel
},
-- DNS
dns = dns,
fakedns = fakedns,
-- 传入连接
inbounds = inbounds,
-- 传出连接
outbounds = outbounds,
-- 路由
routing = routing,
-- 本地策略
--[[
policy = {
levels = {
[0] = {
handshake = 4,
connIdle = 300,
uplinkOnly = 2,
downlinkOnly = 5,
bufferSize = 10240,
statsUserUplink = false,
statsUserDownlink = false
}
},
system = {
statsInboundUplink = false,
statsInboundDownlink = false
}
}
]]--
}
print(jsonc.stringify(config, 1))
end

View File

@ -0,0 +1,111 @@
local api = require "luci.model.cbi.passwall.api.api"
local jsonc = api.jsonc
local inbounds = {}
local outbounds = {}
local routing = nil
local var = api.get_args(arg)
local local_socks_address = var["-local_socks_address"] or "0.0.0.0"
local local_socks_port = var["-local_socks_port"]
local local_socks_username = var["-local_socks_username"]
local local_socks_password = var["-local_socks_password"]
local local_http_address = var["-local_http_address"] or "0.0.0.0"
local local_http_port = var["-local_http_port"]
local local_http_username = var["-local_http_username"]
local local_http_password = var["-local_http_password"]
local server_proto = var["-server_proto"]
local server_address = var["-server_address"]
local server_port = var["-server_port"]
local server_username = var["-server_username"]
local server_password = var["-server_password"]
function gen_outbound(proto, address, port, username, password)
local result = {
protocol = proto,
streamSettings = {
network = "tcp",
security = "none"
},
settings = {
servers = {
{
address = address,
port = tonumber(port),
users = (username and password) and {
{
user = username,
pass = password
}
} or nil
}
}
}
}
return result
end
if local_socks_address and local_socks_port then
local inbound = {
listen = local_socks_address,
port = tonumber(local_socks_port),
protocol = "socks",
settings = {
udp = true,
auth = "noauth"
}
}
if local_socks_username and local_socks_password and local_socks_username ~= "" and local_socks_password ~= "" then
inbound.settings.auth = "password"
inbound.settings.accounts = {
{
user = local_socks_username,
pass = local_socks_password
}
}
end
table.insert(inbounds, inbound)
end
if local_http_address and local_http_port then
local inbound = {
listen = local_http_address,
port = tonumber(local_http_port),
protocol = "http",
settings = {
allowTransparent = false
}
}
if local_http_username and local_http_password and local_http_username ~= "" and local_http_password ~= "" then
inbound.settings.accounts = {
{
user = local_http_username,
pass = local_http_password
}
}
end
table.insert(inbounds, inbound)
end
if server_proto ~= "nil" and server_address ~= "nil" and server_port ~= "nil" then
local outbound = gen_outbound(server_proto, server_address, server_port, server_username, server_password)
if outbound then table.insert(outbounds, outbound) end
end
-- 额外传出连接
table.insert(outbounds, {
protocol = "freedom", tag = "direct", settings = {keep = ""}
})
local config = {
log = {
-- error = string.format("/tmp/etc/passwall/%s.log", node[".name"]),
loglevel = "warning"
},
-- 传入连接
inbounds = inbounds,
-- 传出连接
outbounds = outbounds,
-- 路由
routing = routing
}
print(jsonc.stringify(config, 1))

View File

@ -0,0 +1,134 @@
module("luci.model.cbi.passwall.api.hysteria", package.seeall)
local api = require "luci.model.cbi.passwall.api.api"
local fs = api.fs
local sys = api.sys
local util = api.util
local i18n = api.i18n
local pre_release_url = "https://api.github.com/repos/HyNetwork/hysteria/releases?per_page=1"
local release_url = "https://api.github.com/repos/HyNetwork/hysteria/releases/latest"
local api_url = release_url
local app_path = api.get_hysteria_path() or ""
function check_path()
if app_path == "" then
return {
code = 1,
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", "hysteria")
}
end
return {
code = 0
}
end
function to_check(arch)
local result = check_path()
if result.code ~= 0 then
return result
end
if not arch or arch == "" then arch = api.auto_get_arch() end
local file_tree, sub_version = api.get_file_info(arch)
if file_tree == "" then
return {
code = 1,
error = i18n.translate("Can't determine ARCH, or ARCH not supported.")
}
end
return api.common_to_check(api_url, api.get_hysteria_version(), "linux%-" .. file_tree .. sub_version)
end
function to_download(url, size)
local result = check_path()
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/hysteria_download.*")
local tmp_file = util.trim(util.exec("mktemp -u -t hysteria_download.XXXXXX"))
if size then
local kb1 = api.get_free_space("/tmp")
if tonumber(size) > tonumber(kb1) then
return {code = 1, error = i18n.translatef("%s not enough space.", "/tmp")}
end
end
result = api.exec(api.curl, {api._unpack(api.curl_args), "-o", tmp_file, url}, nil, api.command_timeout) == 0
if not result then
api.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}
end
function to_move(file)
local result = check_path()
if result.code ~= 0 then
return result
end
if not file or file == "" or not fs.access(file) then
sys.call("/bin/rm -rf /tmp/hysteria_download.*")
return {code = 1, error = i18n.translate("Client file is required.")}
end
local new_version = api.get_hysteria_version(file)
if new_version == "" then
sys.call("/bin/rm -rf /tmp/hysteria_download.*")
return {
code = 1,
error = i18n.translate("The client file is not suitable for current device.")
}
end
local flag = sys.call('pgrep -af "passwall/.*hysteria" >/dev/null')
if flag == 0 then
sys.call("/etc/init.d/passwall stop")
end
local old_app_size = 0
if fs.access(app_path) then
old_app_size = api.get_file_space(app_path)
end
local new_app_size = api.get_file_space(file)
local final_dir = api.get_final_dir(app_path)
local final_dir_free_size = api.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("/bin/rm -rf /tmp/hysteria_download.*")
return {code = 1, error = i18n.translatef("%s not enough space.", final_dir)}
end
end
result = api.exec("/bin/mv", {"-f", file, app_path}, nil, api.command_timeout) == 0
sys.call("/bin/rm -rf /tmp/hysteria_download.*")
if flag == 0 then
sys.call("/etc/init.d/passwall 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

View File

@ -0,0 +1,183 @@
module("luci.model.cbi.passwall.api.trojan_go", package.seeall)
local api = require "luci.model.cbi.passwall.api.api"
local fs = api.fs
local sys = api.sys
local util = api.util
local i18n = api.i18n
local pre_release_url = "https://api.github.com/repos/p4gefau1t/trojan-go/releases?per_page=1"
local release_url = "https://api.github.com/repos/p4gefau1t/trojan-go/releases/latest"
local api_url = release_url
local app_path = api.get_trojan_go_path() or ""
function check_path()
if app_path == "" then
return {
code = 1,
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", "Trojan-GO")
}
end
return {
code = 0
}
end
function to_check(arch)
local result = check_path()
if result.code ~= 0 then
return result
end
if not arch or arch == "" then arch = api.auto_get_arch() end
local file_tree, sub_version = api.get_file_info(arch)
if file_tree == "" then
return {
code = 1,
error = i18n.translate("Can't determine ARCH, or ARCH not supported.")
}
end
if file_tree == "mips" then file_tree = "mips%-hardfloat" end
if file_tree == "mipsle" then file_tree = "mipsle%-hardfloat" end
if file_tree == "arm64" then
file_tree = "armv8"
else
if sub_version and sub_version:match("^[5-8]$") then file_tree = file_tree .. "v" .. sub_version end
end
return api.common_to_check(api_url, api.get_trojan_go_version(), "linux%-" .. file_tree .. "%.zip")
end
function to_download(url, size)
local result = check_path()
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/trojan-go_download.*")
local tmp_file = util.trim(util.exec("mktemp -u -t trojan-go_download.XXXXXX"))
if size then
local kb1 = api.get_free_space("/tmp")
if tonumber(size) > tonumber(kb1) then
return {code = 1, error = i18n.translatef("%s not enough space.", "/tmp")}
end
end
result = api.exec(api.curl, {api._unpack(api.curl_args), "-o", tmp_file, url}, nil, api.command_timeout) == 0
if not result then
api.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}
end
function to_extract(file, subfix)
local result = check_path()
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
api.exec("/bin/rm", {"-f", file})
return {
code = 1,
error = i18n.translate("Not installed unzip, Can't unzip!")
}
end
sys.call("/bin/rm -rf /tmp/trojan-go_extract.*")
local new_file_size = api.get_file_space(file)
local tmp_free_size = api.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 trojan-go_extract.XXXXXX"))
local output = {}
api.exec("/usr/bin/unzip", {"-o", file, "-d", tmp_dir},
function(chunk) output[#output + 1] = chunk end)
local files = util.split(table.concat(output))
api.exec("/bin/rm", {"-f", file})
return {code = 0, file = tmp_dir}
end
function to_move(file)
local result = check_path()
if result.code ~= 0 then
return result
end
if not file or file == "" then
sys.call("/bin/rm -rf /tmp/trojan-go_extract.*")
return {code = 1, error = i18n.translate("Client file is required.")}
end
local bin_path = file .. "/trojan-go"
local new_version = api.get_trojan_go_version(bin_path)
if new_version == "" then
sys.call("/bin/rm -rf /tmp/trojan-go_extract.*")
return {
code = 1,
error = i18n.translate("The client file is not suitable for current device.")
}
end
local flag = sys.call('pgrep -af "passwall/.*trojan-go" >/dev/null')
if flag == 0 then
sys.call("/etc/init.d/passwall stop")
end
local old_app_size = 0
if fs.access(app_path) then
old_app_size = api.get_file_space(app_path)
end
local new_app_size = api.get_file_space(bin_path)
local final_dir = api.get_final_dir(app_path)
local final_dir_free_size = api.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("/bin/rm -rf /tmp/trojan-go_extract.*")
return {code = 1, error = i18n.translatef("%s not enough space.", final_dir)}
end
end
result = api.exec("/bin/mv", { "-f", bin_path, app_path }, nil, api.command_timeout) == 0
sys.call("/bin/rm -rf /tmp/trojan-go_extract.*")
if flag == 0 then
sys.call("/etc/init.d/passwall 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

View File

@ -0,0 +1,181 @@
module("luci.model.cbi.passwall.api.v2ray", package.seeall)
local api = require "luci.model.cbi.passwall.api.api"
local fs = api.fs
local sys = api.sys
local util = api.util
local i18n = api.i18n
local pre_release_url = "https://api.github.com/repos/v2fly/v2ray-core/releases?per_page=1"
local release_url = "https://api.github.com/repos/v2fly/v2ray-core/releases/latest"
local api_url = release_url
local app_path = api.get_v2ray_path() or ""
function check_path()
if app_path == "" then
return {
code = 1,
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", "V2ray")
}
end
return {
code = 0
}
end
function to_check(arch)
local result = check_path()
if result.code ~= 0 then
return result
end
if not arch or arch == "" then arch = api.auto_get_arch() end
local file_tree, sub_version = api.get_file_info(arch)
if file_tree == "" then
return {
code = 1,
error = i18n.translate("Can't determine ARCH, or ARCH not supported.")
}
end
if file_tree == "amd64" then file_tree = "64" end
if file_tree == "386" then file_tree = "32" end
if file_tree == "mipsle" then file_tree = "mips32le" end
if file_tree == "mips" then file_tree = "mips32" end
if file_tree == "arm" then file_tree = "arm32" end
return api.common_to_check(api_url, api.get_v2ray_version(), "linux%-" .. file_tree .. (sub_version ~= "" and ".+" .. sub_version or ""))
end
function to_download(url, size)
local result = check_path()
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/v2ray_download.*")
local tmp_file = util.trim(util.exec("mktemp -u -t v2ray_download.XXXXXX"))
if size then
local kb1 = api.get_free_space("/tmp")
if tonumber(size) > tonumber(kb1) then
return {code = 1, error = i18n.translatef("%s not enough space.", "/tmp")}
end
end
result = api.exec(api.curl, {api._unpack(api.curl_args), "-o", tmp_file, url}, nil, api.command_timeout) == 0
if not result then
api.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}
end
function to_extract(file, subfix)
local result = check_path()
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
api.exec("/bin/rm", {"-f", file})
return {
code = 1,
error = i18n.translate("Not installed unzip, Can't unzip!")
}
end
sys.call("/bin/rm -rf /tmp/v2ray_extract.*")
local new_file_size = api.get_file_space(file)
local tmp_free_size = api.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 v2ray_extract.XXXXXX"))
local output = {}
api.exec("/usr/bin/unzip", {"-o", file, "v2ray", "-d", tmp_dir},
function(chunk) output[#output + 1] = chunk end)
local files = util.split(table.concat(output))
api.exec("/bin/rm", {"-f", file})
return {code = 0, file = tmp_dir}
end
function to_move(file)
local result = check_path()
if result.code ~= 0 then
return result
end
if not file or file == "" then
sys.call("/bin/rm -rf /tmp/v2ray_extract.*")
return {code = 1, error = i18n.translate("Client file is required.")}
end
local bin_path = file .. "/v2ray"
local new_version = api.get_v2ray_version(bin_path)
if new_version == "" then
sys.call("/bin/rm -rf /tmp/v2ray_extract.*")
return {
code = 1,
error = i18n.translate("The client file is not suitable for current device.")
}
end
local flag = sys.call('pgrep -af "passwall/.*v2ray" >/dev/null')
if flag == 0 then
sys.call("/etc/init.d/passwall stop")
end
local old_app_size = 0
if fs.access(app_path) then
old_app_size = api.get_file_space(app_path)
end
local new_app_size = api.get_file_space(bin_path)
local final_dir = api.get_final_dir(app_path)
local final_dir_free_size = api.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("/bin/rm -rf /tmp/v2ray_extract.*")
return {code = 1, error = i18n.translatef("%s not enough space.", final_dir)}
end
end
result = api.exec("/bin/mv", { "-f", bin_path, app_path }, nil, api.command_timeout) == 0
sys.call("/bin/rm -rf /tmp/v2ray_extract.*")
if flag == 0 then
sys.call("/etc/init.d/passwall 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

View File

@ -0,0 +1,181 @@
module("luci.model.cbi.passwall.api.xray", package.seeall)
local api = require "luci.model.cbi.passwall.api.api"
local fs = api.fs
local sys = api.sys
local util = api.util
local i18n = api.i18n
local pre_release_url = "https://api.github.com/repos/XTLS/Xray-core/releases?per_page=1"
local release_url = "https://api.github.com/repos/XTLS/Xray-core/releases/latest"
local api_url = release_url
local app_path = api.get_xray_path() or ""
function check_path()
if app_path == "" then
return {
code = 1,
error = i18n.translatef("You did not fill in the %s path. Please save and apply then update manually.", "Xray")
}
end
return {
code = 0
}
end
function to_check(arch)
local result = check_path()
if result.code ~= 0 then
return result
end
if not arch or arch == "" then arch = api.auto_get_arch() end
local file_tree, sub_version = api.get_file_info(arch)
if file_tree == "" then
return {
code = 1,
error = i18n.translate("Can't determine ARCH, or ARCH not supported.")
}
end
if file_tree == "amd64" then file_tree = "64" end
if file_tree == "386" then file_tree = "32" end
if file_tree == "mipsle" then file_tree = "mips32le" end
if file_tree == "mips" then file_tree = "mips32" end
if file_tree == "arm" then file_tree = "arm32" end
return api.common_to_check(api_url, api.get_xray_version(), "linux%-" .. file_tree .. (sub_version ~= "" and ".+" .. sub_version or ""))
end
function to_download(url, size)
local result = check_path()
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/xray_download.*")
local tmp_file = util.trim(util.exec("mktemp -u -t xray_download.XXXXXX"))
if size then
local kb1 = api.get_free_space("/tmp")
if tonumber(size) > tonumber(kb1) then
return {code = 1, error = i18n.translatef("%s not enough space.", "/tmp")}
end
end
result = api.exec(api.curl, {api._unpack(api.curl_args), "-o", tmp_file, url}, nil, api.command_timeout) == 0
if not result then
api.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}
end
function to_extract(file, subfix)
local result = check_path()
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
api.exec("/bin/rm", {"-f", file})
return {
code = 1,
error = i18n.translate("Not installed unzip, Can't unzip!")
}
end
sys.call("/bin/rm -rf /tmp/xray_extract.*")
local new_file_size = api.get_file_space(file)
local tmp_free_size = api.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 xray_extract.XXXXXX"))
local output = {}
api.exec("/usr/bin/unzip", {"-o", file, "xray", "-d", tmp_dir},
function(chunk) output[#output + 1] = chunk end)
local files = util.split(table.concat(output))
api.exec("/bin/rm", {"-f", file})
return {code = 0, file = tmp_dir}
end
function to_move(file)
local result = check_path()
if result.code ~= 0 then
return result
end
if not file or file == "" then
sys.call("/bin/rm -rf /tmp/xray_extract.*")
return {code = 1, error = i18n.translate("Client file is required.")}
end
local bin_path = file .. "/xray"
local new_version = api.get_xray_version(bin_path)
if new_version == "" then
sys.call("/bin/rm -rf /tmp/xray_extract.*")
return {
code = 1,
error = i18n.translate("The client file is not suitable for current device.")
}
end
local flag = sys.call('pgrep -af "passwall/.*xray" >/dev/null')
if flag == 0 then
sys.call("/etc/init.d/passwall stop")
end
local old_app_size = 0
if fs.access(app_path) then
old_app_size = api.get_file_space(app_path)
end
local new_app_size = api.get_file_space(bin_path)
local final_dir = api.get_final_dir(app_path)
local final_dir_free_size = api.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("/bin/rm -rf /tmp/xray_extract.*")
return {code = 1, error = i18n.translatef("%s not enough space.", final_dir)}
end
end
result = api.exec("/bin/mv", { "-f", bin_path, app_path }, nil, api.command_timeout) == 0
sys.call("/bin/rm -rf /tmp/xray_extract.*")
if flag == 0 then
sys.call("/etc/init.d/passwall 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

View File

@ -0,0 +1,123 @@
local api = require "luci.model.cbi.passwall.api.api"
local appname = api.appname
local sys = api.sys
local has_chnlist = api.fs.access("/usr/share/passwall/rules/chnlist")
m = Map(appname)
local global_proxy_mode = (m:get("@global[0]", "tcp_proxy_mode") or "") .. (m:get("@global[0]", "udp_proxy_mode") or "")
-- [[ ACLs Settings ]]--
s = m:section(TypedSection, "acl_rule", translate("ACLs"), "<font color='red'>" .. translate("ACLs is a tools which used to designate specific IP proxy mode.") .. "</font>")
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
function s.remove(e, t)
sys.call("rm -rf /tmp/etc/passwall_tmp/dns_" .. t .. "*")
TypedSection.remove(e, 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
---- TCP Proxy Mode
tcp_proxy_mode = s:option(ListValue, "tcp_proxy_mode", translatef("%s Proxy Mode", "TCP"))
tcp_proxy_mode.default = "default"
tcp_proxy_mode.rmempty = false
tcp_proxy_mode:value("default", translate("Default"))
tcp_proxy_mode:value("disable", translate("No Proxy"))
tcp_proxy_mode:value("global", translate("Global Proxy"))
if has_chnlist and global_proxy_mode:find("returnhome") then
tcp_proxy_mode:value("returnhome", translate("China List"))
else
tcp_proxy_mode:value("gfwlist", translate("GFW List"))
tcp_proxy_mode:value("chnroute", translate("Not China List"))
end
tcp_proxy_mode:value("direct/proxy", translate("Only use direct/proxy list"))
---- UDP Proxy Mode
udp_proxy_mode = s:option(ListValue, "udp_proxy_mode", translatef("%s Proxy Mode", "UDP"))
udp_proxy_mode.default = "default"
udp_proxy_mode.rmempty = false
udp_proxy_mode:value("default", translate("Default"))
udp_proxy_mode:value("disable", translate("No Proxy"))
udp_proxy_mode:value("global", translate("Global Proxy"))
if has_chnlist and global_proxy_mode:find("returnhome") then
udp_proxy_mode:value("returnhome", translate("China List"))
else
udp_proxy_mode:value("gfwlist", translate("GFW List"))
udp_proxy_mode:value("chnroute", translate("Not China List"))
end
udp_proxy_mode:value("direct/proxy", translate("Only use direct/proxy list"))
--[[
---- 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("80,443", "80,443")
o:value("80:65535", "80 " .. translate("or more"))
o:value("1:443", "443 " .. translate("or less"))
---- 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"))
o:value("53", "53")
]]--
return m

View File

@ -0,0 +1,296 @@
local api = require "luci.model.cbi.passwall.api.api"
local appname = api.appname
local sys = api.sys
local has_v2ray = api.is_finded("v2ray")
local has_xray = api.is_finded("xray")
local has_chnlist = api.fs.access("/usr/share/passwall/rules/chnlist")
m = Map(appname)
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
nodes_table[#nodes_table + 1] = e
end
local global_proxy_mode = (m:get("@global[0]", "tcp_proxy_mode") or "") .. (m:get("@global[0]", "udp_proxy_mode") or "")
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
-- [[ 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 Proxy Mode
tcp_proxy_mode = s:option(ListValue, "tcp_proxy_mode", translatef("%s Proxy Mode", "TCP"))
tcp_proxy_mode.default = "default"
tcp_proxy_mode.rmempty = false
tcp_proxy_mode:value("default", translate("Default"))
tcp_proxy_mode:value("disable", translate("No Proxy"))
tcp_proxy_mode:value("global", translate("Global Proxy"))
if has_chnlist and global_proxy_mode:find("returnhome") then
tcp_proxy_mode:value("returnhome", translate("China List"))
else
tcp_proxy_mode:value("gfwlist", translate("GFW List"))
tcp_proxy_mode:value("chnroute", translate("Not China List"))
end
tcp_proxy_mode:value("direct/proxy", translate("Only use direct/proxy list"))
---- UDP Proxy Mode
udp_proxy_mode = s:option(ListValue, "udp_proxy_mode", translatef("%s Proxy Mode", "UDP"))
udp_proxy_mode.default = "default"
udp_proxy_mode.rmempty = false
udp_proxy_mode:value("default", translate("Default"))
udp_proxy_mode:value("disable", translate("No Proxy"))
udp_proxy_mode:value("global", translate("Global Proxy"))
if has_chnlist and global_proxy_mode:find("returnhome") then
udp_proxy_mode:value("returnhome", translate("China List"))
else
udp_proxy_mode:value("gfwlist", translate("GFW List"))
udp_proxy_mode:value("chnroute", translate("Not China List"))
end
udp_proxy_mode:value("direct/proxy", translate("Only use direct/proxy list"))
---- 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 Proxy Drop Ports
o = s:option(Value, "tcp_proxy_drop_ports", translate("TCP Proxy Drop Ports"))
o.default = "default"
o:value("disable", translate("No patterns are used"))
o:value("default", translate("Default"))
---- UDP Proxy Drop Ports
o = s:option(Value, "udp_proxy_drop_ports", translate("UDP Proxy Drop Ports"))
o.default = "default"
o:value("disable", translate("No patterns are used"))
o:value("default", translate("Default"))
o:value("80,443", translate("QUIC"))
---- 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("80,443", "80,443")
o:value("80:65535", "80 " .. translate("or more"))
o:value("1:443", "443 " .. translate("or less"))
---- 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"))
o:value("53", "53")
tcp_node = s:option(ListValue, "tcp_node", "<a style='color: red'>" .. translate("TCP Node") .. "</a>")
tcp_node.default = "default"
tcp_node:value("default", translate("Default"))
udp_node = s:option(ListValue, "udp_node", "<a style='color: red'>" .. translate("UDP Node") .. "</a>")
udp_node.default = "default"
udp_node:value("default", translate("Default"))
udp_node:value("tcp", translate("Same as the tcp node"))
for k, v in pairs(nodes_table) do
tcp_node:value(v.id, v["remark"])
udp_node:value(v.id, v["remark"])
end
---- DNS Forward Mode
o = s:option(ListValue, "dns_mode", translate("Filter Mode"))
o:depends({ tcp_node = "default", ['!reverse'] = true })
if api.is_finded("dns2socks") then
o:value("dns2socks", "dns2socks")
end
if has_v2ray then
o:value("v2ray", "V2ray")
end
if has_xray then
o:value("xray", "Xray")
end
o = s:option(ListValue, "v2ray_dns_mode", " ")
o:value("tcp", "TCP")
o:value("doh", "DoH")
o:depends("dns_mode", "v2ray")
o:depends("dns_mode", "xray")
---- DNS Forward
o = s:option(Value, "dns_forward", translate("Remote DNS"))
o.default = "1.1.1.1"
o:value("1.1.1.1", "1.1.1.1 (CloudFlare DNS)")
o:value("1.1.1.2", "1.1.1.2 (CloudFlare DNS)")
o:value("8.8.8.8", "8.8.8.8 (Google DNS)")
o:value("8.8.4.4", "8.8.4.4 (Google DNS)")
o:value("208.67.222.222", "208.67.222.222 (Open DNS)")
o:value("208.67.220.220", "208.67.220.220 (Open DNS)")
o:depends("dns_mode", "dns2socks")
o:depends("v2ray_dns_mode", "tcp")
if has_v2ray or has_xray then
---- DoH
o = s:option(Value, "dns_doh", translate("DoH request address"))
o:value("https://cloudflare-dns.com/dns-query,1.1.1.1", "CloudFlare")
o:value("https://security.cloudflare-dns.com/dns-query,1.1.1.2", "CloudFlare-Security")
o:value("https://doh.opendns.com/dns-query,208.67.222.222", "OpenDNS")
o:value("https://dns.google/dns-query,8.8.8.8", "Google")
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:value("https://dns.quad9.net/dns-query,9.9.9.9", "Quad9-Recommended")
o:value("https://dns.adguard.com/dns-query,176.103.130.130", "AdGuard")
o.default = "https://cloudflare-dns.com/dns-query,1.1.1.1"
o.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 api.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
o:depends("v2ray_dns_mode", "doh")
end
o = s:option(Value, "dns_client_ip", translate("EDNS Client Subnet"))
o.datatype = "ipaddr"
o:depends("v2ray_dns_mode", "doh")
return m

View File

@ -0,0 +1,44 @@
local api = require "luci.model.cbi.passwall.api.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/v2ray_version"))
s:append(Template(appname .. "/app_update/xray_version"))
s:append(Template(appname .. "/app_update/trojan_go_version"))
s:append(Template(appname .. "/app_update/brook_version"))
s:append(Template(appname .. "/app_update/hysteria_version"))
o = s:option(Value, "v2ray_file", translatef("%s App Path", "V2ray"))
o.default = "/usr/bin/v2ray"
o.rmempty = false
o = s:option(Value, "xray_file", translatef("%s App Path", "Xray"))
o.default = "/usr/bin/xray"
o.rmempty = false
o = s:option(Value, "trojan_go_file", translatef("%s App Path", "Trojan-Go"))
o.default = "/usr/bin/trojan-go"
o.rmempty = false
o = s:option(Value, "brook_file", translatef("%s App Path", "Brook"))
o.default = "/usr/bin/brook"
o.rmempty = false
o = s:option(Value, "hysteria_file", translatef("%s App Path", "Hysteria"))
o.default = "/usr/bin/hysteria"
o.rmempty = false
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

View File

@ -0,0 +1,66 @@
local api = require "luci.model.cbi.passwall.api.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, "tcp_node", "TCP " .. 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", "TCP " .. translate("Restore Switch"), translate("When detects main node is available, switch back to the main node."))
o = s:option(ListValue, "shunt_logic", "TCP " .. translate("If the main node is V2ray/Xray 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

View File

@ -0,0 +1,461 @@
local api = require "luci.model.cbi.passwall.api.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")
local has_chnlist = api.fs.access("/usr/share/passwall/rules/chnlist")
m = Map(appname)
local nodes_table = {}
for k, e in ipairs(api.get_valid_nodes()) do
nodes_table[#nodes_table + 1] = e
end
local socks_table = {}
uci:foreach(appname, "socks", function(s)
if s.enabled == "1" and s.node then
local id, remarks
local same, i = s.node:match("^(tcp)")
if same then
remarks = translatef("Same as the tcp node")
else
for k, n in pairs(nodes_table) do
if (s.node == n.id) then
remarks = n["remark"]; break
end
end
end
id = "127.0.0.1" .. ":" .. s.port
socks_table[#socks_table + 1] = {
id = id,
remarks = id .. " - " .. (remarks or translate("Misconfigured"))
}
end
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
local redir_mode_validate = function(self, value, t)
local tcp_proxy_mode_v = tcp_proxy_mode:formvalue(t) or ""
local udp_proxy_mode_v = udp_proxy_mode:formvalue(t) or ""
local localhost_tcp_proxy_mode_v = localhost_tcp_proxy_mode:formvalue(t) or ""
local localhost_udp_proxy_mode_v = localhost_udp_proxy_mode:formvalue(t) or ""
local s = tcp_proxy_mode_v .. udp_proxy_mode_v .. localhost_tcp_proxy_mode_v .. localhost_udp_proxy_mode_v
if s:find("returnhome") then
if s:find("chnroute") or s:find("gfwlist") then
return nil, translate("China list or gfwlist cannot be used together with outside China list!")
end
end
return value
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
---- TCP Node
tcp_node = s:taboption("Main", ListValue, "tcp_node", "<a style='color: red'>" .. translate("TCP Node") .. "</a>")
tcp_node.description = ""
--tcp_node.description = translate("For proxy specific list.")
--tcp_node.description = o.description .. "<br />"
local current_node = luci.sys.exec(string.format("[ -f '/tmp/etc/%s/id/TCP' ] && echo -n $(cat /tmp/etc/%s/id/TCP)", 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
local remarks = api.get_full_node_remarks(n)
local url = api.url("node_config", current_node)
tcp_node.description = tcp_node.description .. translatef("Current node: %s", string.format('<a href="%s">%s</a>', url, remarks)) .. "<br />"
end
end
end
tcp_node:value("nil", translate("Close"))
-- 分流
if (has_v2ray or has_xray) and #nodes_table > 0 then
local normal_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 == "_shunt" then
shunt_list[#shunt_list + 1] = v
end
end
for k, v in pairs(shunt_list) do
uci:foreach(appname, "shunt_rules", function(e)
local id = e[".name"]
if id and e.remarks then
o = s:taboption("Main", ListValue, v.id .. "." .. id .. "_node", string.format('* <a href="%s" target="_blank">%s</a>', api.url("shunt_rules", id), e.remarks))
o:depends("tcp_node", v.id)
o:value("nil", translate("Close"))
o:value("_default", translate("Default"))
o:value("_direct", translate("Direct Connection"))
o:value("_blackhole", translate("Blackhole"))
for k1, v1 in pairs(normal_list) do
o:value(v1.id, v1["remark"])
end
o.cfgvalue = function(self, section)
return m:get(v.id, id) or "nil"
end
o.write = function(self, section, value)
m:set(v.id, id, value)
end
end
end)
local id = "default_node"
o = s:taboption("Main", ListValue, v.id .. "." .. id, string.format('* <a style="color:red">%s</a>', translate("Default")))
o:depends("tcp_node", v.id)
o:value("_direct", translate("Direct Connection"))
o:value("_blackhole", translate("Blackhole"))
for k1, v1 in pairs(normal_list) do
o:value(v1.id, v1["remark"])
end
o.cfgvalue = function(self, section)
return m:get(v.id, id) or "nil"
end
o.write = function(self, section, value)
m:set(v.id, id, value)
end
local id = "main_node"
o = s:taboption("Main", ListValue, v.id .. "." .. 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:depends("tcp_node", v.id)
o:value("nil", translate("Close"))
for k1, v1 in pairs(normal_list) do
o:value(v1.id, v1["remark"])
end
o.cfgvalue = function(self, section)
return m:get(v.id, id) or "nil"
end
o.write = function(self, section, value)
m:set(v.id, id, value)
end
end
end
udp_node = s:taboption("Main", ListValue, "udp_node", "<a style='color: red'>" .. translate("UDP Node") .. "</a>")
udp_node:value("nil", translate("Close"))
--udp_node.description = translate("For proxy game network.")
udp_node:value("tcp", translate("Same as the tcp node"))
s:tab("DNS", translate("DNS"))
if api.is_finded("smartdns") then
dns_shunt = s:taboption("DNS", ListValue, "dns_shunt", translate("DNS Shunt"))
dns_shunt:value("dnsmasq", "Dnsmasq")
dns_shunt:value("smartdns", "SmartDNS")
group_domestic = s:taboption("DNS", Value, "group_domestic", translate("Domestic group name"))
group_domestic.placeholder = "local"
group_domestic:depends("dns_shunt", "smartdns")
group_domestic.description = translate("You only need to configure domestic DNS packets in SmartDNS and set it redirect or as Dnsmasq upstream, and fill in the domestic DNS group name here.")
end
o = s:taboption("DNS", Flag, "filter_proxy_ipv6", translate("Filter Proxy Host IPv6"), translate("Experimental feature."))
o.default = "0"
---- DNS Forward Mode
dns_mode = s:taboption("DNS", ListValue, "dns_mode", translate("Filter Mode"))
dns_mode.rmempty = false
dns_mode:reset_values()
if api.is_finded("pdnsd") then
dns_mode:value("pdnsd", "pdnsd " .. translatef("Requery DNS By %s", translate("TCP Node")))
end
if api.is_finded("dns2socks") then
dns_mode:value("dns2socks", "dns2socks")
end
if has_v2ray then
dns_mode:value("v2ray", "V2ray")
end
if has_xray then
dns_mode:value("xray", "Xray")
end
dns_mode:value("udp", translatef("Requery DNS By %s", "UDP"))
o = s:taboption("DNS", ListValue, "v2ray_dns_mode", " ")
o:value("tcp", "TCP")
o:value("doh", "DoH")
o:value("fakedns", "FakeDNS")
o:depends("dns_mode", "v2ray")
o:depends("dns_mode", "xray")
o.validate = function(self, value, t)
if value == "fakedns" then
local _dns_mode = dns_mode:formvalue(t)
local _tcp_node = tcp_node:formvalue(t)
if m:get(_tcp_node, "type"):lower() ~= _dns_mode then
return nil, translatef("TCP node must be '%s' type to use FakeDNS.", _dns_mode)
end
end
return value
end
o = s:taboption("DNS", Value, "socks_server", translate("Socks Server"), translate("Make sure socks service is available on this address."))
for k, v in pairs(socks_table) do o:value(v.id, v.remarks) end
o.validate = function(self, value, t)
if not datatypes.ipaddrport(value) then
return nil, translate("Socks Server") .. " " .. translate("Not valid IP format, please re-enter!")
end
return value
end
o:depends({dns_mode = "dns2socks"})
---- DoH
o = s:taboption("DNS", Value, "up_trust_doh", translate("DoH request address"))
o:value("https://cloudflare-dns.com/dns-query,1.1.1.1", "CloudFlare")
o:value("https://security.cloudflare-dns.com/dns-query,1.1.1.2", "CloudFlare-Security")
o:value("https://doh.opendns.com/dns-query,208.67.222.222", "OpenDNS")
o:value("https://dns.google/dns-query,8.8.8.8", "Google")
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:value("https://dns.quad9.net/dns-query,9.9.9.9", "Quad9-Recommended")
o:value("https://dns.adguard.com/dns-query,176.103.130.130", "AdGuard")
o.default = "https://cloudflare-dns.com/dns-query,1.1.1.1"
o.validate = doh_validate
o:depends("v2ray_dns_mode", "doh")
---- DNS Forward
o = s:taboption("DNS", Value, "dns_forward", translate("Remote DNS"))
--o.description = translate("IP:Port mode acceptable, multi value split with english comma.") .. " " .. translate("If you use dns2socks, only the first one is valid.")
o.datatype = "or(ipaddr,ipaddrport)"
o.default = "1.1.1.1"
o:value("1.1.1.1", "1.1.1.1 (CloudFlare DNS)")
o:value("1.1.1.2", "1.1.1.2 (CloudFlare DNS)")
o:value("8.8.8.8", "8.8.8.8 (Google DNS)")
o:value("8.8.4.4", "8.8.4.4 (Google DNS)")
o:value("208.67.222.222", "208.67.222.222 (Open DNS)")
o:value("208.67.220.220", "208.67.220.220 (Open DNS)")
o:depends({dns_mode = "dns2socks"})
o:depends({dns_mode = "pdnsd"})
o:depends({dns_mode = "udp"})
o:depends({v2ray_dns_mode = "tcp"})
o = s:taboption("DNS", Value, "dns_client_ip", translate("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("v2ray_dns_mode", "tcp")
o:depends("v2ray_dns_mode", "doh")
o = s:taboption("DNS", Flag, "dns_cache", translate("Cache Resolved"))
o.default = "1"
o:depends({dns_mode = "dns2socks"})
o:depends({dns_mode = "pdnsd"})
o:depends({dns_mode = "v2ray", v2ray_dns_mode = "tcp"})
o:depends({dns_mode = "v2ray", v2ray_dns_mode = "doh"})
o:depends({dns_mode = "xray", v2ray_dns_mode = "tcp"})
o:depends({dns_mode = "xray", v2ray_dns_mode = "doh"})
o.rmempty = false
if has_chnlist and api.is_finded("chinadns-ng") then
o = s:taboption("DNS", Flag, "chinadns_ng", translate("ChinaDNS-NG"), translate("The effect is better, but will increase the memory."))
o.default = "0"
if api.is_finded("smartdns") then
o:depends({dns_shunt = "dnsmasq", dns_mode = "dns2socks"})
o:depends({dns_shunt = "dnsmasq", dns_mode = "pdnsd"})
o:depends({dns_shunt = "dnsmasq", dns_mode = "v2ray", v2ray_dns_mode = "tcp"})
o:depends({dns_shunt = "dnsmasq", dns_mode = "v2ray", v2ray_dns_mode = "doh"})
o:depends({dns_shunt = "dnsmasq", dns_mode = "xray", v2ray_dns_mode = "tcp"})
o:depends({dns_shunt = "dnsmasq", dns_mode = "xray", v2ray_dns_mode = "doh"})
o:depends({dns_shunt = "dnsmasq", dns_mode = "udp"})
else
o:depends({dns_mode = "dns2socks"})
o:depends({dns_mode = "pdnsd"})
o:depends({dns_mode = "v2ray", v2ray_dns_mode = "tcp"})
o:depends({dns_mode = "v2ray", v2ray_dns_mode = "doh"})
o:depends({dns_mode = "xray", v2ray_dns_mode = "tcp"})
o:depends({dns_mode = "xray", v2ray_dns_mode = "doh"})
o:depends({dns_mode = "udp"})
end
end
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("/usr/share/" .. appname .. "/iptables.sh flush_ipset > /dev/null 2>&1 &")
luci.http.redirect(api.url("log"))
end
s:tab("Proxy", translate("Mode"))
---- TCP Default Proxy Mode
tcp_proxy_mode = s:taboption("Proxy", ListValue, "tcp_proxy_mode", "TCP " .. translate("Default") .. translate("Proxy Mode"))
-- o.description = translate("If not available, try clearing the cache.")
tcp_proxy_mode:value("disable", translate("No Proxy"))
tcp_proxy_mode:value("global", translate("Global Proxy"))
tcp_proxy_mode:value("gfwlist", translate("GFW List"))
tcp_proxy_mode:value("chnroute", translate("Not China List"))
if has_chnlist then
tcp_proxy_mode:value("returnhome", translate("China List"))
end
tcp_proxy_mode:value("direct/proxy", translate("Only use direct/proxy list"))
tcp_proxy_mode.default = "chnroute"
--tcp_proxy_mode.validate = redir_mode_validate
---- UDP Default Proxy Mode
udp_proxy_mode = s:taboption("Proxy", ListValue, "udp_proxy_mode", "UDP " .. translate("Default") .. translate("Proxy Mode"))
udp_proxy_mode:value("disable", translate("No Proxy"))
udp_proxy_mode:value("global", translate("Global Proxy"))
udp_proxy_mode:value("gfwlist", translate("GFW List"))
udp_proxy_mode:value("chnroute", translate("Not China List"))
if has_chnlist then
udp_proxy_mode:value("returnhome", translate("China List"))
end
udp_proxy_mode:value("direct/proxy", translate("Only use direct/proxy list"))
udp_proxy_mode.default = "chnroute"
--udp_proxy_mode.validate = redir_mode_validate
---- Localhost TCP Proxy Mode
localhost_tcp_proxy_mode = s:taboption("Proxy", ListValue, "localhost_tcp_proxy_mode", translate("Router Localhost") .. " TCP " .. translate("Proxy Mode"))
-- o.description = translate("The server client can also use this rule to scientifically surf the Internet.")
localhost_tcp_proxy_mode:value("default", translatef("Same as the %s default proxy mode", "TCP"))
localhost_tcp_proxy_mode:value("global", translate("Global Proxy"))
localhost_tcp_proxy_mode:value("gfwlist", translate("GFW List"))
localhost_tcp_proxy_mode:value("chnroute", translate("Not China List"))
if has_chnlist then
localhost_tcp_proxy_mode:value("returnhome", translate("China List"))
end
localhost_tcp_proxy_mode:value("disable", translate("No Proxy"))
localhost_tcp_proxy_mode:value("direct/proxy", translate("Only use direct/proxy list"))
localhost_tcp_proxy_mode.default = "default"
--localhost_tcp_proxy_mode.validate = redir_mode_validate
---- Localhost UDP Proxy Mode
localhost_udp_proxy_mode = s:taboption("Proxy", ListValue, "localhost_udp_proxy_mode", translate("Router Localhost") .. " UDP " .. translate("Proxy Mode"))
localhost_udp_proxy_mode:value("default", translatef("Same as the %s default proxy mode", "UDP"))
localhost_udp_proxy_mode:value("global", translate("Global Proxy"))
localhost_udp_proxy_mode:value("gfwlist", translate("GFW List"))
localhost_udp_proxy_mode:value("chnroute", translate("Not China List"))
if has_chnlist then
localhost_udp_proxy_mode:value("returnhome", translate("China List"))
end
localhost_udp_proxy_mode:value("disable", translate("No Proxy"))
localhost_udp_proxy_mode:value("direct/proxy", translate("Only use direct/proxy list"))
localhost_udp_proxy_mode.default = "default"
localhost_udp_proxy_mode.validate = redir_mode_validate
tips = s:taboption("Proxy", DummyValue, "tips", " ")
tips.rawhtml = true
tips.cfgvalue = function(t, n)
return string.format('<a style="color: red" href="%s">%s</a>', api.url("acl"), translate("Want different devices to use different proxy modes/ports/nodes? Please use access control."))
end
s:tab("log", translate("Log"))
o = s:taboption("log", Flag, "close_log_tcp", translatef("%s Node Log Close", "TCP"))
o.rmempty = false
o = s:taboption("log", Flag, "close_log_udp", translatef("%s Node Log Close", "UDP"))
o.rmempty = false
loglevel = s:taboption("log", ListValue, "loglevel", "V2ray/Xray" .. translate("Log Level"))
loglevel.default = "warning"
loglevel:value("debug")
loglevel:value("info")
loglevel:value("warning")
loglevel:value("error")
trojan_loglevel = s:taboption("log", ListValue, "trojan_loglevel", "Trojan" .. translate("Log Level"))
trojan_loglevel.default = "2"
trojan_loglevel:value("0", "all")
trojan_loglevel:value("1", "info")
trojan_loglevel:value("2", "warn")
trojan_loglevel:value("3", "error")
trojan_loglevel:value("4", "fatal")
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_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"))
socks_node:value("tcp", translate("Same as the tcp node"))
local n = 0
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
tcp_node:value(v.id, v["remark"])
udp_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

View File

@ -0,0 +1,121 @@
local api = require "luci.model.cbi.passwall.api.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)
-- [[ 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, otherwise problems can arise!") ..
"</font>")
s.template = "cbi/tblsection"
s.sortable = true
s.anonymous = true
s.addremove = true
s.create = function(e, t)
TypedSection.create(e, api.gen_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

View File

@ -0,0 +1,8 @@
local api = require "luci.model.cbi.passwall.api.api"
local appname = api.appname
f = SimpleForm(appname)
f.reset = false
f.submit = false
f:append(Template(appname .. "/log/log"))
return f

View File

@ -0,0 +1,863 @@
local api = require "luci.model.cbi.passwall.api.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"
}
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"
}
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, "passwall", " ")
share.rawhtml = true
share.template = "passwall/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("ipt2socks") then
type:value("Socks", translate("Socks"))
end
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("trojan-plus") or api.is_finded("trojan") then
type:value("Trojan", translate("Trojan"))
end
]]--
if api.is_finded("trojan-plus") then
type:value("Trojan-Plus", translate("Trojan-Plus"))
end
if api.is_finded("trojan-go") then
type:value("Trojan-Go", translate("Trojan-Go"))
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("_balancing", translate("Balancing"))
protocol:value("_shunt", translate("Shunt"))
protocol:depends("type", "V2ray")
protocol:depends("type", "Xray")
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"],
remarks = e["remark"]
}
end
end
-- 负载均衡列表
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")
-- 分流
uci:foreach(appname, "shunt_rules", function(e)
if e[".name"] and e.remarks then
o = s:option(ListValue, e[".name"], string.format('* <a href="%s" target="_blank">%s</a>', api.url("shunt_rules", e[".name"]), e.remarks))
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
_proxy_tag = s:option(ListValue, e[".name"] .. "_proxy_tag", string.format('* <a style="color:red">%s</a>', e.remarks .. " " .. translate("Preproxy")))
_proxy_tag:value("nil", translate("Close"))
_proxy_tag:value("default", translate("Default"))
_proxy_tag:value("main", translate("Default Preproxy"))
_proxy_tag.default = "nil"
for k, v in pairs(nodes_table) do
o:value(v.id, v.remarks)
_proxy_tag:depends(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")
default_node = s:option(ListValue, "default_node", string.format('* <a style="color:red">%s</a>', translate("Default")))
default_node:value("_direct", translate("Direct Connection"))
default_node:value("_blackhole", translate("Blackhole"))
for k, v in pairs(nodes_table) do default_node:value(v.id, v.remarks) end
default_node:depends("protocol", "_shunt")
if #nodes_table > 0 then
o = s:option(ListValue, "main_node", 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:value("nil", translate("Close"))
for k, v in pairs(nodes_table) do
o:value(v.id, v.remarks)
o:depends("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", "_balancing")
domainStrategy:depends("protocol", "_shunt")
domainMatcher = s:option(ListValue, "domainMatcher", translate("Domain matcher"))
domainMatcher:value("hybrid")
domainMatcher:value("linear")
domainMatcher:depends("protocol", "_balancing")
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", "Socks")
address:depends("type", "SS")
address:depends("type", "SS-Rust")
address:depends("type", "SSR")
address:depends("type", "Brook")
address:depends("type", "Trojan")
address:depends("type", "Trojan-Plus")
address:depends("type", "Trojan-Go")
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" })
--[[
use_ipv6 = s:option(Flag, "use_ipv6", translate("Use IPv6"))
use_ipv6.default = 0
use_ipv6:depends("type", "Socks")
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", "Trojan")
use_ipv6:depends("type", "Trojan-Plus")
use_ipv6:depends("type", "Trojan-Go")
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", "Socks")
port:depends("type", "SS")
port:depends("type", "SS-Rust")
port:depends("type", "SSR")
port:depends("type", "Brook")
port:depends("type", "Trojan")
port:depends("type", "Trojan-Plus")
port:depends("type", "Trojan-Go")
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" })
username = s:option(Value, "username", translate("Username"))
username:depends("type", "Socks")
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", "Socks")
password:depends("type", "SS")
password:depends("type", "SS-Rust")
password:depends("type", "SSR")
password:depends("type", "Brook")
password:depends("type", "Trojan")
password:depends("type", "Trojan-Plus")
password:depends("type", "Trojan-Go")
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" })
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", translate("TCP 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")
tcp_fast_open:depends("type", "Trojan")
tcp_fast_open:depends("type", "Trojan-Plus")
tcp_fast_open:depends("type", "Trojan-Go")
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.validate = function(self, value, t)
if value then
local type = type:formvalue(t) or ""
if value == "0" and (type == "Trojan" or type == "Trojan-Plus") then
return nil, translate("Original Trojan only supported 'tls', please choose 'tls'.")
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 = "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" })
tls:depends("type", "Trojan")
tls:depends("type", "Trojan-Plus")
tls:depends("type", "Trojan-Go")
xtls = s:option(Flag, "xtls", translate("XTLS"))
xtls.default = 0
xtls:depends({ type = "Xray", protocol = "vless", tls = true })
xtls:depends({ type = "Xray", protocol = "trojan", tls = true })
flow = s:option(Value, "flow", translate("flow"))
flow.default = "xtls-rprx-direct"
flow:value("xtls-rprx-origin")
flow:value("xtls-rprx-origin-udp443")
flow:value("xtls-rprx-direct")
flow:value("xtls-rprx-direct-udp443")
flow:value("xtls-rprx-splice")
flow:value("xtls-rprx-splice-udp443")
flow:depends("xtls", true)
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 })
-- [[ TLS部分 ]] --
tls_sessionTicket = s:option(Flag, "tls_sessionTicket", translate("Session Ticket"))
tls_sessionTicket.default = "0"
tls_sessionTicket:depends({ type = "Trojan", tls = true })
tls_sessionTicket:depends({ type = "Trojan-Plus", tls = true })
tls_sessionTicket:depends({ type = "Trojan-Go", tls = true })
trojan_go_fingerprint = s:option(ListValue, "trojan_go_fingerprint", translate("Finger Print"))
trojan_go_fingerprint:value("disable", translate("Disable"))
trojan_go_fingerprint:value("firefox")
trojan_go_fingerprint:value("chrome")
trojan_go_fingerprint:value("ios")
trojan_go_fingerprint.default = "disable"
trojan_go_fingerprint:depends({ type = "Trojan-Go", tls = true })
function trojan_go_fingerprint.cfgvalue(self, section)
return m:get(section, "fingerprint")
end
function trojan_go_fingerprint.write(self, section, value)
m:set(section, "fingerprint", value)
end
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)
tls_allowInsecure:depends("type", "Hysteria")
xray_fingerprint = s:option(ListValue, "xray_fingerprint", translate("Finger Print"))
xray_fingerprint:value("disable", translate("Disable"))
xray_fingerprint:value("chrome")
xray_fingerprint:value("firefox")
xray_fingerprint:value("safari")
xray_fingerprint:value("randomized")
xray_fingerprint.default = "disable"
xray_fingerprint:depends({ type = "Xray", tls = true, xtls = 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
trojan_transport = s:option(ListValue, "trojan_transport", translate("Transport"))
trojan_transport:value("original", translate("Original"))
trojan_transport:value("ws", "WebSocket")
trojan_transport.default = "original"
trojan_transport:depends("type", "Trojan-Go")
trojan_plugin = s:option(ListValue, "plugin_type", translate("Transport Plugin"))
trojan_plugin:value("plaintext", "Plain Text")
trojan_plugin:value("shadowsocks", "ShadowSocks")
trojan_plugin:value("other", "Other")
trojan_plugin.default = "plaintext"
trojan_plugin:depends({ tls = false, trojan_transport = "original" })
trojan_plugin_cmd = s:option(Value, "plugin_cmd", translate("Plugin Binary"))
trojan_plugin_cmd.placeholder = "eg: /usr/bin/v2ray-plugin"
trojan_plugin_cmd:depends({ plugin_type = "shadowsocks" })
trojan_plugin_cmd:depends({ plugin_type = "other" })
trojan_plugin_op = s:option(Value, "plugin_option", translate("Plugin Option"))
trojan_plugin_op.placeholder = "eg: obfs=http;obfs-host=www.baidu.com"
trojan_plugin_op:depends({ plugin_type = "shadowsocks" })
trojan_plugin_op:depends({ plugin_type = "other" })
trojan_plugin_arg = s:option(DynamicList, "plugin_arg", translate("Plugin Option Args"))
trojan_plugin_arg.placeholder = "eg: [\"-config\", \"test.json\"]"
trojan_plugin_arg:depends({ plugin_type = "shadowsocks" })
trojan_plugin_arg:depends({ plugin_type = "other" })
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" })
]]--
-- [[ 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")
-- [[ WebSocket部分 ]]--
ws_host = s:option(Value, "ws_host", translate("WebSocket Host"))
ws_host:depends("transport", "ws")
ws_host:depends("ss_transport", "ws")
ws_host:depends("trojan_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("trojan_transport", "ws")
ws_path:depends({ type = "Brook", brook_protocol = "wsclient" })
ws_enableEarlyData = s:option(Flag, "ws_enableEarlyData", translate("Enable early data"))
ws_enableEarlyData:depends("transport", "ws")
ws_maxEarlyData = s:option(Value, "ws_maxEarlyData", translate("Early data length"))
ws_maxEarlyData.default = "1024"
ws_maxEarlyData:depends("ws_enableEarlyData", true)
function ws_maxEarlyData.cfgvalue(self, section)
return m:get(section, "ws_maxEarlyData")
end
function ws_maxEarlyData.write(self, section, value)
m:set(section, "ws_maxEarlyData", value)
end
-- [[ 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")
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"})
-- [[ Trojan-Go Shadowsocks2 ]] --
ss_aead = s:option(Flag, "ss_aead", translate("Shadowsocks secondary encryption"))
ss_aead:depends("type", "Trojan-Go")
ss_aead.default = "0"
ss_aead_method = s:option(ListValue, "ss_aead_method", translate("Encrypt Method"))
for _, v in ipairs(encrypt_methods_ss_aead) do ss_aead_method:value(v, v) end
ss_aead_method.default = "aes-128-gcm"
ss_aead_method:depends("ss_aead", "1")
ss_aead_pwd = s:option(Value, "ss_aead_pwd", translate("Password"))
ss_aead_pwd.password = true
ss_aead_pwd:depends("ss_aead", "1")
-- [[ Trojan-Go Mux ]]--
mux = s:option(Flag, "smux", translate("Smux"))
mux:depends("type", "Trojan-Go")
-- [[ Mux ]]--
mux = s:option(Flag, "mux", translate("Mux"))
mux:depends({ type = "V2ray", protocol = "vmess" })
mux:depends({ type = "V2ray", protocol = "vless", xtls = false })
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", xtls = false })
mux:depends({ type = "Xray", protocol = "http" })
mux:depends({ type = "Xray", protocol = "socks" })
mux:depends({ type = "Xray", protocol = "shadowsocks" })
mux:depends({ type = "Xray", protocol = "trojan" })
mux_concurrency = s:option(Value, "mux_concurrency", translate("Mux concurrency"))
mux_concurrency.default = 8
mux_concurrency:depends("mux", true)
mux_concurrency:depends("smux", 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_disable_mtu_discovery = s:option(Flag, "hysteria_disable_mtu_discovery", translate("Disable MTU detection"))
hysteria_disable_mtu_discovery: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

View File

@ -0,0 +1,151 @@
local api = require "luci.model.cbi.passwall.api.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_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, "haproxy_config", function(s)
if s["lbss"] and s["lbss"] == t then
m:del(s[".name"])
end
end)
m.uci:foreach(appname, "acl_rule", function(s)
if s["tcp_node"] and s["tcp_node"] == t then
m:set(s[".name"], "tcp_node", "default")
end
if s["udp_node"] and s["udp_node"] == t then
m:set(s[".name"], "udp_node", "default")
end
end)
for k, v in ipairs(m:get("@auto_switch[0]", "tcp_node") or {}) do
if v and v == t then
sys.call(string.format("uci -q del_list %s.@auto_switch[0].tcp_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]", "tcp_node") or "nil") == t then
m:set('@global[0]', "tcp_node", new_node)
end
if (m:get("@global[0]", "udp_node") or "nil") == t then
m:set('@global[0]', "udp_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

View File

@ -0,0 +1,142 @@
local api = require "luci.model.cbi.passwall.api.api"
local appname = api.appname
local has_ss = api.is_finded("ss-redir")
local has_ss_rust = api.is_finded("sslocal")
local has_trojan_plus = api.is_finded("trojan-plus")
local has_v2ray = api.is_finded("v2ray")
local has_xray = api.is_finded("xray")
local has_trojan_go = api.is_finded("trojan-go")
local ss_aead_type = {}
local trojan_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_trojan_plus then
trojan_type[#trojan_type + 1] = "trojan-plus"
end
if has_v2ray then
trojan_type[#trojan_type + 1] = "v2ray"
ss_aead_type[#ss_aead_type + 1] = "v2ray"
end
if has_xray then
trojan_type[#trojan_type + 1] = "xray"
ss_aead_type[#ss_aead_type + 1] = "xray"
end
if has_trojan_go then
trojan_type[#trojan_type + 1] = "trojan-go"
end
m = Map(appname)
-- [[ Subscribe Settings ]]--
s = m:section(TypedSection, "global_subscribe", "")
s.anonymous = true
---- Subscribe via proxy
o = s:option(Flag, "subscribe_proxy", translate("Subscribe via proxy"))
o.default = 0
o.rmempty = false
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
if #trojan_type > 0 then
o = s:option(ListValue, "trojan_type", translate("Trojan Node Use Type"))
for key, value in pairs(trojan_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

View File

@ -0,0 +1,110 @@
local api = require "luci.model.cbi.passwall.api.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_trojan_plus = api.is_finded("trojan-plus")
local has_v2ray = api.is_finded("v2ray")
local has_xray = api.is_finded("xray")
local has_trojan_go = api.is_finded("trojan-go")
local ss_aead_type = {}
local trojan_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_trojan_plus then
trojan_type[#trojan_type + 1] = "trojan-plus"
end
if has_v2ray then
trojan_type[#trojan_type + 1] = "v2ray"
ss_aead_type[#ss_aead_type + 1] = "v2ray"
end
if has_xray then
trojan_type[#trojan_type + 1] = "xray"
ss_aead_type[#ss_aead_type + 1] = "xray"
end
if has_trojan_go then
trojan_type[#trojan_type + 1] = "trojan-go"
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
if #trojan_type > 0 then
o = s:option(ListValue, "trojan_type", translate("Trojan Node Use Type"))
o.default = "global"
o:value("global", translate("Use global config"))
for key, value in pairs(trojan_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

View File

@ -0,0 +1,148 @@
local api = require "luci.model.cbi.passwall.api.api"
local appname = api.appname
local fs = api.fs
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 Proxy Drop Ports
o = s:option(Value, "tcp_proxy_drop_ports", translate("TCP Proxy Drop Ports"))
o.default = "disable"
o:value("disable", translate("No patterns are used"))
---- UDP Proxy Drop Ports
o = s:option(Value, "udp_proxy_drop_ports", translate("UDP Proxy Drop Ports"))
o.default = "80,443"
o:value("disable", translate("No patterns are used"))
o:value("80,443", translate("QUIC"))
---- 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"))
o:value("80:65535", "80 " .. translate("or more"))
o:value("1:443", "443 " .. translate("or less"))
---- UDP Redir Ports
o = s:option(Value, "udp_redir_ports", translate("UDP Redir Ports"))
o.default = "1:65535"
o:value("1:65535", translate("All"))
o:value("53", "DNS")
if os.execute("lsmod | grep -i REDIRECT >/dev/null") == 0 and os.execute("lsmod | grep -i 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 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
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
o = s:option(Flag, "route_only", translate("Sniffing Route Only (Xray)"), translate("When enabled, the server not will resolve the domain name again."))
o.default = 0
o:depends("sniffing", true)
local domains_excluded = string.format("/usr/share/%s/rules/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) fs.writefile(domains_excluded, "") end
o:depends({sniffing = true, route_only = false})
return m

View File

@ -0,0 +1,91 @@
local api = require "luci.model.cbi.passwall.api.api"
local appname = api.appname
local has_v2ray = api.is_finded("v2ray")
local has_xray = api.is_finded("xray")
m = Map(appname)
-- [[ Rule Settings ]]--
s = m:section(TypedSection, "global_rules", translate("Rule status"))
s.anonymous = true
--[[
o = s:option(Flag, "adblock", translate("Enable adblock"))
o.rmempty = false
]]--
---- gfwlist URL
o = s:option(DynamicList, "gfwlist_url", translate("GFW domains(gfwlist) Update URL"))
o:value("https://cdn.jsdelivr.net/gh/YW5vbnltb3Vz/domain-list-community@release/gfwlist.txt", translate("v2fly/domain-list-community"))
o:value("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/gfw.txt", translate("Loyalsoldier/v2ray-rules-dat"))
o:value("https://cdn.jsdelivr.net/gh/Loukky/gfwlist-by-loukky/gfwlist.txt", translate("Loukky/gfwlist-by-loukky"))
o:value("https://cdn.jsdelivr.net/gh/gfwlist/gfwlist/gfwlist.txt", translate("gfwlist/gfwlist"))
o.default = "https://cdn.jsdelivr.net/gh/Loukky/gfwlist-by-loukky/gfwlist.txt"
----chnroute URL
o = s:option(DynamicList, "chnroute_url", translate("China IPs(chnroute) Update URL"))
o:value("https://ispip.clang.cn/all_cn.txt", translate("Clang.CN"))
o:value("https://ispip.clang.cn/all_cn_cidr.txt", translate("Clang.CN.CIDR"))
o:value("https://cdn.jsdelivr.net/gh/soffchen/GeoIP2-CN@release/CN-ip-cidr.txt", translate("soffchen/GeoIP2-CN"))
o:value("https://cdn.jsdelivr.net/gh/Hackl0us/GeoIP2-CN@release/CN-ip-cidr.txt", translate("Hackl0us/GeoIP2-CN"))
o.default = "https://ispip.clang.cn/all_cn.txt"
----chnroute6 URL
o = s:option(DynamicList, "chnroute6_url", translate("China IPv6s(chnroute6) Update URL"))
o:value("https://ispip.clang.cn/all_cn_ipv6.txt", translate("Clang.CN.IPv6"))
o.default = "https://ispip.clang.cn/all_cn_ipv6.txt"
----chnlist URL
o = s:option(DynamicList, "chnlist_url", translate("China List(Chnlist) Update URL"))
o:value("https://cdn.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/accelerated-domains.china.conf", translate("felixonmars/domains.china"))
o:value("https://cdn.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/apple.china.conf", translate("felixonmars/apple.china"))
o:value("https://cdn.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/google.china.conf", translate("felixonmars/google.china"))
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)
if has_v2ray or has_xray then
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
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"))
end
return m

View File

@ -0,0 +1,265 @@
local api = require "luci.model.cbi.passwall.api.api"
local appname = api.appname
local fs = api.fs
local sys = api.sys
local datatypes = api.datatypes
local path = string.format("/usr/share/%s/rules/", appname)
local route_hosts_path = "/etc/"
m = SimpleForm(appname)
m.uci = api.uci
-- [[ Rule List Settings ]]--
s = m:section(TypedSection, "global_rules")
s.anonymous = true
s:tab("direct_list", translate("Direct List"))
s:tab("proxy_list", translate("Proxy List"))
s:tab("block_list", translate("Block List"))
s:tab("lan_ip_list", translate("Lan IP List"))
s:tab("route_hosts", translate("Route Hosts"))
---- Direct Hosts
local direct_host = path .. "direct_host"
o = s:taboption("direct_list", TextValue, "direct_host", "", "<font color='red'>" .. translate("Join the direct hosts list of domain names will not proxy.") .. "</font>")
o.rows = 15
o.wrap = "off"
o.cfgvalue = function(self, section)
return fs.readfile(direct_host) or ""
end
o.write = function(self, section, value)
fs.writefile(direct_host, value:gsub("\r\n", "\n"))
sys.call("rm -rf /tmp/etc/passwall_tmp/dns_*")
end
o.remove = function(self, section, value)
fs.writefile(direct_host, "")
sys.call("rm -rf /tmp/etc/passwall_tmp/dns_*")
end
o.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
if host:find("#") and host:find("#") == 1 then
return value
end
if not datatypes.hostname(host) then
return nil, host .. " " .. translate("Not valid domain name, please re-enter!")
end
end
return value
end
---- Direct IP
local direct_ip = path .. "direct_ip"
o = s:taboption("direct_list", TextValue, "direct_ip", "", "<font color='red'>" .. translate("These had been joined ip addresses will not proxy. Please input the ip address or ip address segment,every line can input only one ip address. For example: 192.168.0.0/24 or 223.5.5.5.") .. "</font>")
o.rows = 15
o.wrap = "off"
o.cfgvalue = function(self, section)
return fs.readfile(direct_ip) or ""
end
o.write = function(self, section, value)
fs.writefile(direct_ip, value:gsub("\r\n", "\n"))
end
o.remove = function(self, section, value)
fs.writefile(direct_ip, "")
end
o.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("#") and ipmask:find("#") == 1 then
return value
end
if not ( datatypes.ipmask4(ipmask) or datatypes.ipmask6(ipmask) ) then
return nil, ipmask .. " " .. translate("Not valid IP format, please re-enter!")
end
end
return value
end
---- Proxy Hosts
local proxy_host = path .. "proxy_host"
o = s:taboption("proxy_list", TextValue, "proxy_host", "", "<font color='red'>" .. translate("These had been joined websites will use proxy. Please input the domain names of websites, every line can input only one website domain. For example: google.com.") .. "</font>")
o.rows = 15
o.wrap = "off"
o.cfgvalue = function(self, section)
return fs.readfile(proxy_host) or ""
end
o.write = function(self, section, value)
fs.writefile(proxy_host, value:gsub("\r\n", "\n"))
sys.call("rm -rf /tmp/etc/passwall_tmp/dns_*")
end
o.remove = function(self, section, value)
fs.writefile(proxy_host, "")
sys.call("rm -rf /tmp/etc/passwall_tmp/dns_*")
end
o.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
if host:find("#") and host:find("#") == 1 then
return value
end
if not datatypes.hostname(host) then
return nil, host .. " " .. translate("Not valid domain name, please re-enter!")
end
end
return value
end
---- Proxy IP
local proxy_ip = path .. "proxy_ip"
o = s:taboption("proxy_list", TextValue, "proxy_ip", "", "<font color='red'>" .. translate("These had been joined ip addresses will use proxy. Please input the ip address or ip address segment, every line can input only one ip address. For example: 35.24.0.0/24 or 8.8.4.4.") .. "</font>")
o.rows = 15
o.wrap = "off"
o.cfgvalue = function(self, section)
return fs.readfile(proxy_ip) or ""
end
o.write = function(self, section, value)
fs.writefile(proxy_ip, value:gsub("\r\n", "\n"))
end
o.remove = function(self, section, value)
fs.writefile(proxy_ip, "")
end
o.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("#") and ipmask:find("#") == 1 then
return value
end
if not ( datatypes.ipmask4(ipmask) or datatypes.ipmask6(ipmask) ) then
return nil, ipmask .. " " .. translate("Not valid IP format, please re-enter!")
end
end
return value
end
---- Block Hosts
local block_host = path .. "block_host"
o = s:taboption("block_list", TextValue, "block_host", "", "<font color='red'>" .. translate("These had been joined websites will be block. Please input the domain names of websites, every line can input only one website domain. For example: twitter.com.") .. "</font>")
o.rows = 15
o.wrap = "off"
o.cfgvalue = function(self, section)
return fs.readfile(block_host) or ""
end
o.write = function(self, section, value)
fs.writefile(block_host, value:gsub("\r\n", "\n"))
end
o.remove = function(self, section, value)
fs.writefile(block_host, "")
end
o.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
if host:find("#") and host:find("#") == 1 then
return value
end
if not datatypes.hostname(host) then
return nil, host .. " " .. translate("Not valid domain name, please re-enter!")
end
end
return value
end
---- Block IP
local block_ip = path .. "block_ip"
o = s:taboption("block_list", TextValue, "block_ip", "", "<font color='red'>" .. translate("These had been joined ip addresses will be block. Please input the ip address or ip address segment, every line can input only one ip address.") .. "</font>")
o.rows = 15
o.wrap = "off"
o.cfgvalue = function(self, section)
return fs.readfile(block_ip) or ""
end
o.write = function(self, section, value)
fs.writefile(block_ip, value:gsub("\r\n", "\n"))
end
o.remove = function(self, section, value)
fs.writefile(block_ip, "")
end
o.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("#") and ipmask:find("#") == 1 then
return value
end
if not ( datatypes.ipmask4(ipmask) or datatypes.ipmask6(ipmask) ) then
return nil, ipmask .. " " .. translate("Not valid IP format, please re-enter!")
end
end
return value
end
---- Lan IPv4
local lanlist_ipv4 = path .. "lanlist_ipv4"
o = s:taboption("lan_ip_list", TextValue, "lanlist_ipv4", "", "<font color='red'>" .. translate("The list is the IPv4 LAN IP list, which represents the direct connection IP of the LAN. If you need the LAN IP in the proxy list, please clear it from the list. Do not modify this list by default.") .. "</font>")
o.rows = 15
o.wrap = "off"
o.cfgvalue = function(self, section)
return fs.readfile(lanlist_ipv4) or ""
end
o.write = function(self, section, value)
fs.writefile(lanlist_ipv4, value:gsub("\r\n", "\n"))
end
o.remove = function(self, section, value)
fs.writefile(lanlist_ipv4, "")
end
o.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("#") and ipmask:find("#") == 1 then
return value
end
if not datatypes.ipmask4(ipmask) then
return nil, ipmask .. " " .. translate("Not valid IPv4 format, please re-enter!")
end
end
return value
end
---- Lan IPv6
local lanlist_ipv6 = path .. "lanlist_ipv6"
o = s:taboption("lan_ip_list", TextValue, "lanlist_ipv6", "", "<font color='red'>" .. translate("The list is the IPv6 LAN IP list, which represents the direct connection IP of the LAN. If you need the LAN IP in the proxy list, please clear it from the list. Do not modify this list by default.") .. "</font>")
o.rows = 15
o.wrap = "off"
o.cfgvalue = function(self, section)
return fs.readfile(lanlist_ipv6) or ""
end
o.write = function(self, section, value)
fs.writefile(lanlist_ipv6, value:gsub("\r\n", "\n"))
end
o.remove = function(self, section, value)
fs.writefile(lanlist_ipv6, "")
end
o.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("#") and ipmask:find("#") == 1 then
return value
end
if not datatypes.ipmask6(ipmask) then
return nil, ipmask .. " " .. translate("Not valid IPv6 format, please re-enter!")
end
end
return value
end
---- Route Hosts
local hosts = route_hosts_path .. "hosts"
o = s:taboption("route_hosts", TextValue, "hosts", "", "<font color='red'>" .. translate("Configure routing etc/hosts file, if you don't know what you are doing, please don't change the content.") .. "</font>")
o.rows = 15
o.wrap = "off"
o.cfgvalue = function(self, section)
return fs.readfile(hosts) or ""
end
o.write = function(self, section, value)
fs.writefile(hosts, value:gsub("\r\n", "\n"))
end
o.remove = function(self, section, value)
fs.writefile(hosts, "")
end
return m

View File

@ -0,0 +1,79 @@
local api = require "luci.model.cbi.passwall.api.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")
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

View File

@ -0,0 +1,197 @@
#!/usr/bin/lua
local action = arg[1]
local api = require "luci.model.cbi.passwall.api.api"
local sys = api.sys
local uci = api.uci
local jsonc = api.jsonc
local CONFIG = "passwall_server"
local CONFIG_PATH = "/tmp/etc/" .. CONFIG
local LOG_APP_FILE = "/tmp/log/" .. CONFIG .. ".log"
local TMP_BIN_PATH = CONFIG_PATH .. "/bin"
local require_dir = "luci.model.cbi.passwall.server.api."
local ipt_bin = sys.exec("echo -n $(/usr/share/passwall/iptables.sh get_ipt_bin)")
local ip6t_bin = sys.exec("echo -n $(/usr/share/passwall/iptables.sh get_ip6t_bin)")
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)
cmd(ipt_bin .. " -w " .. arg)
end
local function ip6t(arg)
cmd(ip6t_bin .. " -w " .. arg)
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 "PSW-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
f:write(ipt_bin .. '-save -c | grep -v "PSW-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 "PSW-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()
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))
ipt("-N PSW-SERVER")
ipt("-I INPUT -j PSW-SERVER")
ip6t("-N PSW-SERVER")
ip6t("-I INPUT -j PSW-SERVER")
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 .. "shadowsocks").gen_config(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 == "V2ray" then
config = require(require_dir .. "v2ray").gen_config(user)
bin = ln_run(api.get_v2ray_path(), "v2ray", "-config=" .. config_file, log_path)
elseif type == "Xray" then
config = require(require_dir .. "v2ray").gen_config(user)
bin = ln_run(api.get_xray_path(), "xray", "-config=" .. config_file, log_path)
elseif type == "Trojan" then
config = require(require_dir .. "trojan").gen_config(user)
bin = ln_run("/usr/sbin/trojan", "trojan", "-c " .. config_file, log_path)
elseif type == "Trojan-Plus" then
config = require(require_dir .. "trojan").gen_config(user)
bin = ln_run("/usr/sbin/trojan-plus", "trojan-plus", "-c " .. config_file, log_path)
elseif type == "Trojan-Go" then
config = require(require_dir .. "trojan").gen_config(user)
bin = ln_run(api.get_trojan_go_path(), "trojan-go", "-config " .. 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_brook_path(), "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 .. "hysteria").gen_config(user)
bin = ln_run(api.get_hysteria_path(), "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
ipt(string.format('-A PSW-SERVER -p tcp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks))
ip6t(string.format('-A PSW-SERVER -p tcp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks))
if udp_forward == 1 then
ipt(string.format('-A PSW-SERVER -p udp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks))
ip6t(string.format('-A PSW-SERVER -p udp --dport %s -m comment --comment "%s" -j ACCEPT', port, remarks))
end
end
end
end)
gen_include()
end
local function stop()
cmd(string.format("/bin/top -bn1 | grep -v 'grep' | grep '%s/' | awk '{print $1}' | xargs kill -9 >/dev/null 2>&1", CONFIG_PATH))
ipt("-D INPUT -j PSW-SERVER 2>/dev/null")
ipt("-F PSW-SERVER 2>/dev/null")
ipt("-X PSW-SERVER 2>/dev/null")
ip6t("-D INPUT -j PSW-SERVER 2>/dev/null")
ip6t("-F PSW-SERVER 2>/dev/null")
ip6t("-X PSW-SERVER 2>/dev/null")
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

View File

@ -0,0 +1,24 @@
module("luci.model.cbi.passwall.server.api.hysteria", package.seeall)
function gen_config(user)
local config = {
listen = ":" .. user.port,
protocol = user.protocol or "udp",
obfs = user.hysteria_obfs,
cert = user.tls_certificateFile,
key = user.tls_keyFile,
auth = (user.hysteria_auth_type == "string") and {
mode = "password",
config = {
password = user.hysteria_auth_password
}
} or nil,
disable_udp = (user.hysteria_udp == "0") and true or false,
alpn = user.hysteria_alpn or nil,
up_mbps = tonumber(user.hysteria_up_mbps) or 10,
down_mbps = tonumber(user.hysteria_down_mbps) or 50,
recv_window_conn = (user.hysteria_recv_window_conn) and tonumber(user.hysteria_recv_window_conn) or nil,
recv_window = (user.hysteria_recv_window) and tonumber(user.hysteria_recv_window) or nil,
disable_mtu_discovery = (user.hysteria_disable_mtu_discovery) and true or false
}
return config
end

View File

@ -0,0 +1,19 @@
module("luci.model.cbi.passwall.server.api.shadowsocks", package.seeall)
function gen_config(user)
local config = {}
config.server = {"[::0]", "0.0.0.0"}
config.server_port = tonumber(user.port)
config.password = user.password
config.timeout = tonumber(user.timeout)
config.fast_open = (user.tcp_fast_open and user.tcp_fast_open == "1") and true or false
config.method = user.method
if user.type == "SSR" then
config.protocol = user.protocol
config.protocol_param = user.protocol_param
config.obfs = user.obfs
config.obfs_param = user.obfs_param
end
return config
end

View File

@ -0,0 +1,61 @@
module("luci.model.cbi.passwall.server.api.trojan", package.seeall)
function gen_config(user)
local cipher = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:AES128-SHA:AES256-SHA:DES-CBC3-SHA"
local cipher13 = "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384"
local config = {
run_type = "server",
local_addr = "::",
local_port = tonumber(user.port),
remote_addr = (user.remote_enable == "1" and user.remote_address) and user.remote_address or nil,
remote_port = (user.remote_enable == "1" and user.remote_port) and tonumber(user.remote_port) or nil,
password = user.uuid,
log_level = (user.log and user.log == "1") and tonumber(user.loglevel) or 5,
ssl = {
cert = user.tls_certificateFile,
key = user.tls_keyFile,
key_password = "",
cipher = cipher,
cipher_tls13 = cipher13,
prefer_server_cipher = true,
reuse_session = true,
session_ticket = (user.tls_sessionTicket == "1") and true or false,
session_timeout = 600,
plain_http_response = "",
curves = "",
dhparam = ""
},
tcp = {
prefer_ipv4 = false,
no_delay = true,
keep_alive = true,
reuse_port = false,
fast_open = (user.tcp_fast_open and user.tcp_fast_open == "1") and true or false,
fast_open_qlen = 20
}
}
if user.type == "Trojan-Go" then
config.ssl.cipher = nil
config.ssl.cipher_tls13 = nil
config.udp_timeout = 60
config.disable_http_check = true
config.transport_plugin = ((user.tls == nil or user.tls ~= "1") and user.trojan_transport == "original") and {
enabled = user.plugin_type ~= nil,
type = user.plugin_type or "plaintext",
command = user.plugin_type ~= "plaintext" and user.plugin_cmd or nil,
option = user.plugin_type ~= "plaintext" and user.plugin_option or nil,
arg = user.plugin_type ~= "plaintext" and { user.plugin_arg } or nil,
env = {}
} or nil
config.websocket = (user.trojan_transport == 'ws') and {
enabled = true,
path = user.ws_path or "/",
host = user.ws_host or ""
} or nil
config.shadowsocks = (user.ss_aead == "1") and {
enabled = true,
method = user.ss_aead_method or "aes_128_gcm",
password = user.ss_aead_pwd or ""
} or nil
end
return config
end

View File

@ -0,0 +1,246 @@
module("luci.model.cbi.passwall.server.api.v2ray", package.seeall)
local uci = require"luci.model.uci".cursor()
function gen_config(user)
local settings = nil
local routing = nil
local outbounds = {
{protocol = "freedom", tag = "direct"}, {protocol = "blackhole", tag = "blocked"}
}
if user.protocol == "vmess" or user.protocol == "vless" then
if user.uuid then
local clients = {}
for i = 1, #user.uuid do
clients[i] = {
id = user.uuid[i],
flow = ("1" == user.xtls) and user.flow or nil
}
end
settings = {
clients = clients,
decryption = user.decryption or "none"
}
end
elseif user.protocol == "socks" then
settings = {
udp = ("1" == user.udp_forward) and true or false,
auth = ("1" == user.auth) and "password" or "noauth",
accounts = ("1" == user.auth) and {
{
user = user.username,
pass = user.password
}
} or nil
}
elseif user.protocol == "http" then
settings = {
allowTransparent = false,
accounts = ("1" == user.auth) and {
{
user = user.username,
pass = user.password
}
} or nil
}
user.transport = "tcp"
user.tcp_guise = "none"
elseif user.protocol == "shadowsocks" then
settings = {
method = user.method,
password = user.password,
ivCheck = ("1" == user.iv_check) and true or false,
network = user.ss_network or "TCP,UDP"
}
elseif user.protocol == "trojan" then
if user.uuid then
local clients = {}
for i = 1, #user.uuid do
clients[i] = {
flow = ("1" == user.xtls) and user.flow or nil,
password = user.uuid[i],
}
end
settings = {
clients = clients
}
end
elseif user.protocol == "mtproto" then
settings = {
users = {
{
secret = (user.password == nil) and "" or user.password
}
}
}
elseif user.protocol == "dokodemo-door" then
settings = {
network = user.d_protocol,
address = user.d_address,
port = tonumber(user.d_port)
}
end
if user.fallback and user.fallback == "1" then
local fallbacks = {}
for i = 1, #user.fallback_list do
local fallbackStr = user.fallback_list[i]
if fallbackStr then
local tmp = {}
string.gsub(fallbackStr, '[^' .. "," .. ']+', function(w)
table.insert(tmp, w)
end)
local dest = tmp[1] or ""
local path = tmp[2]
if dest:find("%.") then
else
dest = tonumber(dest)
end
fallbacks[i] = {
path = path,
dest = dest,
xver = 1
}
end
end
settings.fallbacks = fallbacks
end
routing = {
domainStrategy = "IPOnDemand",
rules = {
{
type = "field",
ip = {"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"},
outboundTag = (user.accept_lan == nil or user.accept_lan == "0") and "blocked" or "direct"
}
}
}
if user.transit_node and user.transit_node ~= "nil" then
local transit_node_t = uci:get_all("passwall", user.transit_node)
if user.transit_node == "_socks" or user.transit_node == "_http" then
transit_node_t = {
type = user.type,
protocol = user.transit_node:gsub("_", ""),
transport = "tcp",
address = user.transit_node_address,
port = user.transit_node_port,
username = (user.transit_node_username and user.transit_node_username ~= "") and user.transit_node_username or nil,
password = (user.transit_node_password and user.transit_node_password ~= "") and user.transit_node_password or nil,
}
end
local outbound = require("luci.model.cbi.passwall.api.gen_v2ray").gen_outbound(transit_node_t, "transit")
if outbound then
table.insert(outbounds, 1, outbound)
end
end
local config = {
log = {
-- error = "/tmp/etc/passwall_server/log/" .. user[".name"] .. ".log",
loglevel = ("1" == user.log) and user.loglevel or "none"
},
-- 传入连接
inbounds = {
{
listen = (user.bind_local == "1") and "127.0.0.1" or nil,
port = tonumber(user.port),
protocol = user.protocol,
settings = settings,
streamSettings = {
network = user.transport,
security = "none",
xtlsSettings = ("1" == user.tls and "1" == user.xtls) and {
disableSystemRoot = false,
certificates = {
{
certificateFile = user.tls_certificateFile,
keyFile = user.tls_keyFile
}
}
} or nil,
tlsSettings = ("1" == user.tls) and {
disableSystemRoot = false,
certificates = {
{
certificateFile = user.tls_certificateFile,
keyFile = user.tls_keyFile
}
}
} or nil,
tcpSettings = (user.transport == "tcp") and {
acceptProxyProtocol = (user.acceptProxyProtocol and user.acceptProxyProtocol == "1") and true or false,
header = {
type = user.tcp_guise,
request = (user.tcp_guise == "http") and {
path = user.tcp_guise_http_path or {"/"},
headers = {
Host = user.tcp_guise_http_host or {}
}
} or nil
}
} or nil,
kcpSettings = (user.transport == "mkcp") and {
mtu = tonumber(user.mkcp_mtu),
tti = tonumber(user.mkcp_tti),
uplinkCapacity = tonumber(user.mkcp_uplinkCapacity),
downlinkCapacity = tonumber(user.mkcp_downlinkCapacity),
congestion = (user.mkcp_congestion == "1") and true or false,
readBufferSize = tonumber(user.mkcp_readBufferSize),
writeBufferSize = tonumber(user.mkcp_writeBufferSize),
seed = (user.mkcp_seed and user.mkcp_seed ~= "") and user.mkcp_seed or nil,
header = {type = user.mkcp_guise}
} or nil,
wsSettings = (user.transport == "ws") and {
acceptProxyProtocol = (user.acceptProxyProtocol and user.acceptProxyProtocol == "1") and true or false,
headers = (user.ws_host) and {Host = user.ws_host} or nil,
path = user.ws_path
} or nil,
httpSettings = (user.transport == "h2") and {
path = user.h2_path, host = user.h2_host
} or nil,
dsSettings = (user.transport == "ds") and {
path = user.ds_path
} or nil,
quicSettings = (user.transport == "quic") and {
security = user.quic_security,
key = user.quic_key,
header = {type = user.quic_guise}
} or nil,
grpcSettings = (user.transport == "grpc") and {
serviceName = user.grpc_serviceName
} or nil
}
}
},
-- 传出连接
outbounds = outbounds,
routing = routing
}
local alpn = {}
if user.alpn then
string.gsub(user.alpn, '[^' .. "," .. ']+', function(w)
table.insert(alpn, w)
end)
end
if alpn and #alpn > 0 then
if config.inbounds[1].streamSettings.tlsSettings then
config.inbounds[1].streamSettings.tlsSettings.alpn = alpn
end
if config.inbounds[1].streamSettings.xtlsSettings then
config.inbounds[1].streamSettings.xtlsSettings.alpn = alpn
end
end
if "1" == user.tls then
config.inbounds[1].streamSettings.security = "tls"
if user.type == "Xray" and user.xtls and user.xtls == "1" then
config.inbounds[1].streamSettings.security = "xtls"
config.inbounds[1].streamSettings.tlsSettings = nil
end
end
return config
end

View File

@ -0,0 +1,73 @@
local api = require "luci.model.cbi.passwall.api.api"
m = Map("passwall_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("passwall/server/log"))
m:append(Template("passwall/server/users_list_status"))
return m

View File

@ -0,0 +1,728 @@
local api = require "luci.model.cbi.passwall.api.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 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"
}
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("passwall_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("microsocks") then
type:value("Socks", translate("Socks"))
end
if api.is_finded("ss-server") then
type:value("SS", translate("Shadowsocks"))
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("trojan-plus") or api.is_finded("trojan") then
type:value("Trojan", translate("Trojan"))
end
]]--
if api.is_finded("trojan-plus") then
type:value("Trojan-Plus", translate("Trojan-Plus"))
end
if api.is_finded("trojan-go") then
type:value("Trojan-Go", translate("Trojan-Go"))
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", "Socks")
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", "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
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", "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" })
uuid:depends("type", "Trojan")
uuid:depends("type", "Trojan-Go")
uuid:depends("type", "Trojan-Plus")
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 == "0" and (type == "Trojan" or type == "Trojan-Plus") then
return nil, translate("Original Trojan only supported 'tls', please choose 'tls'.")
end
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" })
tls:depends("type", "Trojan")
tls:depends("type", "Trojan-Plus")
tls:depends("type", "Trojan-Go")
xtls = s:option(Flag, "xtls", translate("XTLS"))
xtls.default = 0
xtls:depends({ type = "Xray", protocol = "vless", tls = true })
xtls:depends({ type = "Xray", protocol = "trojan", tls = true })
flow = s:option(Value, "flow", translate("flow"))
flow.default = "xtls-rprx-direct"
flow:value("xtls-rprx-origin")
flow:value("xtls-rprx-direct")
flow:depends("xtls", true)
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")
tls_sessionTicket = s:option(Flag, "tls_sessionTicket", translate("Session Ticket"))
tls_sessionTicket.default = "0"
tls_sessionTicket:depends({ type = "Trojan", tls = true })
tls_sessionTicket:depends({ type = "Trojan-Plus", tls = true })
tls_sessionTicket:depends({ type = "Trojan-Go", tls = true })
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" })
trojan_transport = s:option(ListValue, "trojan_transport", translate("Transport"))
trojan_transport:value("original", translate("Original"))
trojan_transport:value("ws", "WebSocket")
trojan_transport.default = "original"
trojan_transport:depends("type", "Trojan-Go")
trojan_plugin = s:option(ListValue, "plugin_type", translate("Transport Plugin"))
trojan_plugin:value("plaintext", "Plain Text")
trojan_plugin:value("shadowsocks", "ShadowSocks")
trojan_plugin:value("other", "Other")
trojan_plugin.default = "plaintext"
trojan_plugin:depends({ tls = false, trojan_transport = "original" })
trojan_plugin_cmd = s:option(Value, "plugin_cmd", translate("Plugin Binary"))
trojan_plugin_cmd.placeholder = "eg: /usr/bin/v2ray-plugin"
trojan_plugin_cmd:depends({ plugin_type = "shadowsocks" })
trojan_plugin_cmd:depends({ plugin_type = "other" })
trojan_plugin_op = s:option(Value, "plugin_option", translate("Plugin Option"))
trojan_plugin_op.placeholder = "eg: obfs=http;obfs-host=www.baidu.com"
trojan_plugin_op:depends({ plugin_type = "shadowsocks" })
trojan_plugin_op:depends({ plugin_type = "other" })
trojan_plugin_arg = s:option(DynamicList, "plugin_arg", translate("Plugin Option Args"))
trojan_plugin_arg.placeholder = "eg: [\"-config\", \"test.json\"]"
trojan_plugin_arg:depends({ plugin_type = "shadowsocks" })
trojan_plugin_arg:depends({ plugin_type = "other" })
-- [[ WebSocket部分 ]]--
ws_host = s:option(Value, "ws_host", translate("WebSocket Host"))
ws_host:depends("transport", "ws")
ws_host:depends("ss_transport", "ws")
ws_host:depends("trojan_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("trojan_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_host:depends("trojan_transport", "h2")
h2_path = s:option(Value, "h2_path", translate("HTTP/2 Path"))
h2_path:depends("transport", "h2")
h2_path:depends("ss_transport", "h2")
h2_path:depends("trojan_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)
ss_aead = s:option(Flag, "ss_aead", translate("Shadowsocks secondary encryption"))
ss_aead:depends("type", "Trojan-Go")
ss_aead.default = "0"
ss_aead_method = s:option(ListValue, "ss_aead_method", translate("Encrypt Method"))
for _, v in ipairs(encrypt_methods_ss_aead) do ss_aead_method:value(v, v) end
ss_aead_method.default = "aes-128-gcm"
ss_aead_method:depends("ss_aead", true)
ss_aead_pwd = s:option(Value, "ss_aead_pwd", translate("Password"))
ss_aead_pwd.password = true
ss_aead_pwd:depends("ss_aead", 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", "SSR")
tcp_fast_open:depends("type", "Trojan")
tcp_fast_open:depends("type", "Trojan-Plus")
tcp_fast_open:depends("type", "Trojan-Go")
remote_enable = s:option(Flag, "remote_enable", translate("Enable Remote"), translate("You can forward to Nginx/Caddy/V2ray/Xray WebSocket and more."))
remote_enable.default = "1"
remote_enable.rmempty = false
remote_enable:depends("type", "Trojan")
remote_enable:depends("type", "Trojan-Plus")
remote_enable:depends("type", "Trojan-Go")
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", protocol = "vmess" })
accept_lan:depends({ type = "V2ray", protocol = "vless" })
accept_lan:depends({ type = "V2ray", protocol = "http" })
accept_lan:depends({ type = "V2ray", protocol = "socks" })
accept_lan:depends({ type = "V2ray", protocol = "shadowsocks" })
accept_lan:depends({ type = "V2ray", protocol = "trojan" })
accept_lan:depends({ type = "Xray", protocol = "vmess" })
accept_lan:depends({ type = "Xray", protocol = "vless" })
accept_lan:depends({ type = "Xray", protocol = "http" })
accept_lan:depends({ type = "Xray", protocol = "socks" })
accept_lan:depends({ type = "Xray", protocol = "shadowsocks" })
accept_lan:depends({ type = "Xray", protocol = "trojan" })
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
transit_node = s:option(ListValue, "transit_node", translate("transit node"))
transit_node:value("nil", translate("Close"))
transit_node:value("_socks", translate("Custom Socks"))
transit_node:value("_http", translate("Custom HTTP"))
for k, v in pairs(nodes_table) do transit_node:value(v.id, v.remarks) end
transit_node.default = "nil"
transit_node:depends("type", "V2ray")
transit_node:depends("type", "Xray")
transit_node_address = s:option(Value, "transit_node_address", translate("Address (Support Domain Name)"))
transit_node_address:depends("transit_node", "_socks")
transit_node_address:depends("transit_node", "_http")
transit_node_port = s:option(Value, "transit_node_port", translate("Port"))
transit_node_port.datatype = "port"
transit_node_port:depends("transit_node", "_socks")
transit_node_port:depends("transit_node", "_http")
transit_node_username = s:option(Value, "transit_node_username", translate("Username"))
transit_node_username:depends("transit_node", "_socks")
transit_node_username:depends("transit_node", "_http")
transit_node_password = s:option(Value, "transit_node_password", translate("Password"))
transit_node_password.password = true
transit_node_password:depends("transit_node", "_socks")
transit_node_password:depends("transit_node", "_http")
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 })
trojan_loglevel = s:option(ListValue, "trojan_loglevel", translate("Log Level"))
trojan_loglevel.default = "2"
trojan_loglevel:value("0", "all")
trojan_loglevel:value("1", "info")
trojan_loglevel:value("2", "warn")
trojan_loglevel:value("3", "error")
trojan_loglevel:value("4", "fatal")
function trojan_loglevel.cfgvalue(self, section)
return m:get(section, "loglevel")
end
function trojan_loglevel.write(self, section, value)
m:set(section, "loglevel", value)
end
trojan_loglevel:depends({ type = "Trojan", log = true })
trojan_loglevel:depends({ type = "Trojan-Plus", log = true })
trojan_loglevel:depends({ type = "Trojan-Go", log = true })
return m

View File

@ -0,0 +1,159 @@
<%
local api = require "luci.model.cbi.passwall.api.api"
local brook_version = api.get_brook_version()
-%>
<script type="text/javascript">
//<![CDATA[
var brookInfo;
var tokenStr = '<%=token%>';
var manuallyUpdateText = '<%:Manually 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 () {
var brookCheckBtn = document.getElementById('_brook-check_btn');
var brookDetailElm = document.getElementById('_brook-check_btn-detail');
};
function addPageNotice_brook() {
window.onbeforeunload = function (e) {
e.returnValue = updateInProgressNotice;
return updateInProgressNotice;
};
}
function removePageNotice_brook() {
window.onbeforeunload = undefined;
}
function onUpdateSuccess_brook(btn) {
alert(updateSuccessText);
if (btn) {
btn.value = updateSuccessText;
btn.placeholder = updateSuccessText;
btn.disabled = true;
}
window.setTimeout(function () {
window.location.reload();
}, 1000);
}
function onRequestError_brook(btn, errorMessage) {
btn.disabled = false;
btn.value = manuallyUpdateText;
if (errorMessage) {
alert(errorMessage);
}
}
function onBtnClick_brook(btn) {
if (brookInfo === undefined) {
checkUpdate_brook(btn);
} else {
doUpdate_brook(btn);
}
}
function checkUpdate_brook(btn) {
btn.disabled = true;
btn.value = inProgressText;
addPageNotice_brook();
var ckeckDetailElm = document.getElementById(btn.id + '-detail');
XHR.get('<%=api.url("brook_check")%>', {
token: tokenStr,
arch: ''
}, function (x, json) {
removePageNotice_brook();
if (json.code) {
brookInfo = undefined;
onRequestError_brook(btn, json.error);
} else {
if (json.has_update) {
brookInfo = 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_brook(btn) {
btn.disabled = true;
btn.value = downloadingText;
addPageNotice_brook();
var brookUpdateUrl = '<%=api.url("brook_update")%>';
// Download file
XHR.get(brookUpdateUrl, {
token: tokenStr,
url: brookInfo ? brookInfo.data.browser_download_url : '',
size: brookInfo ? brookInfo.data.size / 1024 : null
}, function (x, json) {
if (json.code) {
removePageNotice_brook();
onRequestError_brook(btn, json.error);
} else {
btn.value = decompressioningText;
// Move file to target dir
XHR.get(brookUpdateUrl, {
token: tokenStr,
task: 'move',
file: json.file
}, function (x, json) {
removePageNotice_brook();
if (json.code) {
onRequestError_brook(btn, json.error);
} else {
onUpdateSuccess_brook(btn);
}
}, 300)
}
}, 300)
}
//]]>
</script>
<div class="cbi-value">
<label class="cbi-value-title">Brook
<%:Version%>
</label>
<div class="cbi-value-field">
<div class="cbi-value-description">
<span><%=brook_version ~="" and brook_version or translate("Null") %> 】</span>
<input class="btn cbi-button cbi-button-apply" type="button" id="_brook-check_btn"
onclick="onBtnClick_brook(this);" value="<%:Manually update%>" />
<span id="_brook-check_btn-detail"></span>
</div>
</div>
</div>

View File

@ -0,0 +1,159 @@
<%
local api = require "luci.model.cbi.passwall.api.api"
local hysteria_version = api.get_hysteria_version()
-%>
<script type="text/javascript">
//<![CDATA[
var hysteriaInfo;
var tokenStr = '<%=token%>';
var manuallyUpdateText = '<%:Manually 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 () {
var hysteriaCheckBtn = document.getElementById('_hysteria-check_btn');
var hysteriaDetailElm = document.getElementById('_hysteria-check_btn-detail');
};
function addPageNotice_hysteria() {
window.onbeforeunload = function (e) {
e.returnValue = updateInProgressNotice;
return updateInProgressNotice;
};
}
function removePageNotice_hysteria() {
window.onbeforeunload = undefined;
}
function onUpdateSuccess_hysteria(btn) {
alert(updateSuccessText);
if (btn) {
btn.value = updateSuccessText;
btn.placeholder = updateSuccessText;
btn.disabled = true;
}
window.setTimeout(function () {
window.location.reload();
}, 1000);
}
function onRequestError_hysteria(btn, errorMessage) {
btn.disabled = false;
btn.value = manuallyUpdateText;
if (errorMessage) {
alert(errorMessage);
}
}
function onBtnClick_hysteria(btn) {
if (hysteriaInfo === undefined) {
checkUpdate_hysteria(btn);
} else {
doUpdate_hysteria(btn);
}
}
function checkUpdate_hysteria(btn) {
btn.disabled = true;
btn.value = inProgressText;
addPageNotice_hysteria();
var ckeckDetailElm = document.getElementById(btn.id + '-detail');
XHR.get('<%=api.url("hysteria_check")%>', {
token: tokenStr,
arch: ''
}, function (x, json) {
removePageNotice_hysteria();
if (json.code) {
hysteriaInfo = undefined;
onRequestError_hysteria(btn, json.error);
} else {
if (json.has_update) {
hysteriaInfo = 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_hysteria(btn) {
btn.disabled = true;
btn.value = downloadingText;
addPageNotice_hysteria();
var hysteriaUpdateUrl = '<%=api.url("hysteria_update")%>';
// Download file
XHR.get(hysteriaUpdateUrl, {
token: tokenStr,
url: hysteriaInfo ? hysteriaInfo.data.browser_download_url : '',
size: hysteriaInfo ? hysteriaInfo.data.size / 1024 : null
}, function (x, json) {
if (json.code) {
removePageNotice_hysteria();
onRequestError_hysteria(btn, json.error);
} else {
btn.value = decompressioningText;
// Move file to target dir
XHR.get(hysteriaUpdateUrl, {
token: tokenStr,
task: 'move',
file: json.file
}, function (x, json) {
removePageNotice_hysteria();
if (json.code) {
onRequestError_hysteria(btn, json.error);
} else {
onUpdateSuccess_hysteria(btn);
}
}, 300)
}
}, 300)
}
//]]>
</script>
<div class="cbi-value">
<label class="cbi-value-title">Hysteria
<%:Version%>
</label>
<div class="cbi-value-field">
<div class="cbi-value-description">
<span><%=hysteria_version ~="" and hysteria_version or translate("Null") %> 】</span>
<input class="btn cbi-button cbi-button-apply" type="button" id="_hysteria-check_btn"
onclick="onBtnClick_hysteria(this);" value="<%:Manually update%>" />
<span id="_hysteria-check_btn-detail"></span>
</div>
</div>
</div>

View File

@ -0,0 +1,175 @@
<%
local api = require "luci.model.cbi.passwall.api.api"
local trojan_go_version = api.get_trojan_go_version()
-%>
<script type="text/javascript">
//<![CDATA[
var trojanInfo;
var tokenStr = '<%=token%>';
var manuallyUpdateText = '<%:Manually 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 () {
var trojanCheckBtn = document.getElementById('_trojan-check_btn');
var trojanDetailElm = document.getElementById('_trojan-check_btn-detail');
};
function addPageNotice_trojan() {
window.onbeforeunload = function (e) {
e.returnValue = updateInProgressNotice;
return updateInProgressNotice;
};
}
function removePageNotice_trojan() {
window.onbeforeunload = undefined;
}
function onUpdateSuccess_trojan(btn) {
alert(updateSuccessText);
if (btn) {
btn.value = updateSuccessText;
btn.placeholder = updateSuccessText;
btn.disabled = true;
}
window.setTimeout(function () {
window.location.reload();
}, 1000);
}
function onRequestError_trojan(btn, errorMessage) {
btn.disabled = false;
btn.value = manuallyUpdateText;
if (errorMessage) {
alert(errorMessage);
}
}
function onBtnClick_trojan(btn) {
if (trojanInfo === undefined) {
checkUpdate_trojan(btn);
} else {
doUpdate_trojan(btn);
}
}
function checkUpdate_trojan(btn) {
btn.disabled = true;
btn.value = inProgressText;
addPageNotice_trojan();
var ckeckDetailElm = document.getElementById(btn.id + '-detail');
XHR.get('<%=api.url("trojan_go_check")%>', {
token: tokenStr,
arch: ''
}, function (x, json) {
removePageNotice_trojan();
if (json.code) {
trojanInfo = undefined;
onRequestError_trojan(btn, json.error);
} else {
if (json.has_update) {
trojanInfo = 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_trojan(btn) {
btn.disabled = true;
btn.value = downloadingText;
addPageNotice_trojan();
var trojanUpdateUrl = '<%=api.url("trojan_go_update")%>';
// Download file
XHR.get(trojanUpdateUrl, {
token: tokenStr,
url: trojanInfo ? trojanInfo.data.browser_download_url : '',
size: trojanInfo ? trojanInfo.data.size / 1024 : null
}, function (x, json) {
if (json.code) {
removePageNotice_trojan();
onRequestError_trojan(btn, json.error);
} else {
btn.value = decompressioningText;
// Extract file
XHR.get(trojanUpdateUrl, {
token: tokenStr,
task: 'extract',
file: json.file,
subfix: trojanInfo ? trojanInfo.type : ''
}, function (x, json) {
if (json.code) {
removePageNotice_trojan();
onRequestError_trojan(btn, json.error);
} else {
btn.value = movingText;
// Move file to target dir
XHR.get(trojanUpdateUrl, {
token: tokenStr,
task: 'move',
file: json.file
}, function (x, json) {
removePageNotice_trojan();
if (json.code) {
onRequestError_trojan(btn, json.error);
} else {
onUpdateSuccess_trojan(btn);
}
}, 300)
}
}, 300)
}
}, 300)
}
//]]>
</script>
<div class="cbi-value">
<label class="cbi-value-title">Trojan-Go
<%:Version%>
</label>
<div class="cbi-value-field">
<div class="cbi-value-description">
<span><%=trojan_go_version ~="" and trojan_go_version or translate("Null") %> 】</span>
<input class="btn cbi-button cbi-button-apply" type="button" id="_trojan-check_btn"
onclick="onBtnClick_trojan(this);" value="<%:Manually update%>" />
<span id="_trojan-check_btn-detail"></span>
</div>
</div>
</div>

View File

@ -0,0 +1,175 @@
<%
local api = require "luci.model.cbi.passwall.api.api"
local v2ray_version = api.get_v2ray_version()
-%>
<script type="text/javascript">
//<![CDATA[
var v2rayInfo;
var tokenStr = '<%=token%>';
var manuallyUpdateText = '<%:Manually 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 () {
var v2rayCheckBtn = document.getElementById('_v2ray-check_btn');
var v2rayDetailElm = document.getElementById('_v2ray-check_btn-detail');
};
function addPageNotice_v2ray() {
window.onbeforeunload = function (e) {
e.returnValue = updateInProgressNotice;
return updateInProgressNotice;
};
}
function removePageNotice_v2ray() {
window.onbeforeunload = undefined;
}
function onUpdateSuccess_v2ray(btn) {
alert(updateSuccessText);
if (btn) {
btn.value = updateSuccessText;
btn.placeholder = updateSuccessText;
btn.disabled = true;
}
window.setTimeout(function () {
window.location.reload();
}, 1000);
}
function onRequestError_v2ray(btn, errorMessage) {
btn.disabled = false;
btn.value = manuallyUpdateText;
if (errorMessage) {
alert(errorMessage);
}
}
function onBtnClick_v2ray(btn) {
if (v2rayInfo === undefined) {
checkUpdate_v2ray(btn);
} else {
doUpdate_v2ray(btn);
}
}
function checkUpdate_v2ray(btn) {
btn.disabled = true;
btn.value = inProgressText;
addPageNotice_v2ray();
var ckeckDetailElm = document.getElementById(btn.id + '-detail');
XHR.get('<%=api.url("v2ray_check")%>', {
token: tokenStr,
arch: ''
}, function (x, json) {
removePageNotice_v2ray();
if (json.code) {
v2rayInfo = undefined;
onRequestError_v2ray(btn, json.error);
} else {
if (json.has_update) {
v2rayInfo = 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_v2ray(btn) {
btn.disabled = true;
btn.value = downloadingText;
addPageNotice_v2ray();
var v2rayUpdateUrl = '<%=api.url("v2ray_update")%>';
// Download file
XHR.get(v2rayUpdateUrl, {
token: tokenStr,
url: v2rayInfo ? v2rayInfo.data.browser_download_url : '',
size: v2rayInfo ? v2rayInfo.data.size / 1024 : null
}, function (x, json) {
if (json.code) {
removePageNotice_v2ray();
onRequestError_v2ray(btn, json.error);
} else {
btn.value = decompressioningText;
// Extract file
XHR.get(v2rayUpdateUrl, {
token: tokenStr,
task: 'extract',
file: json.file,
subfix: v2rayInfo ? v2rayInfo.type : ''
}, function (x, json) {
if (json.code) {
removePageNotice_v2ray();
onRequestError_v2ray(btn, json.error);
} else {
btn.value = movingText;
// Move file to target dir
XHR.get(v2rayUpdateUrl, {
token: tokenStr,
task: 'move',
file: json.file
}, function (x, json) {
removePageNotice_v2ray();
if (json.code) {
onRequestError_v2ray(btn, json.error);
} else {
onUpdateSuccess_v2ray(btn);
}
}, 300)
}
}, 300)
}
}, 300)
}
//]]>
</script>
<div class="cbi-value">
<label class="cbi-value-title">V2ray
<%:Version%>
</label>
<div class="cbi-value-field">
<div class="cbi-value-description">
<span><%=v2ray_version ~="" and v2ray_version or translate("Null") %> 】</span>
<input class="btn cbi-button cbi-button-apply" type="button" id="_v2ray-check_btn"
onclick="onBtnClick_v2ray(this);" value="<%:Manually update%>" />
<span id="_v2ray-check_btn-detail"></span>
</div>
</div>
</div>

View File

@ -0,0 +1,175 @@
<%
local api = require "luci.model.cbi.passwall.api.api"
local xray_version = api.get_xray_version()
-%>
<script type="text/javascript">
//<![CDATA[
var xrayInfo;
var tokenStr = '<%=token%>';
var manuallyUpdateText = '<%:Manually 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 () {
var xrayCheckBtn = document.getElementById('_xray-check_btn');
var xrayDetailElm = document.getElementById('_xray-check_btn-detail');
};
function addPageNotice_xray() {
window.onbeforeunload = function (e) {
e.returnValue = updateInProgressNotice;
return updateInProgressNotice;
};
}
function removePageNotice_xray() {
window.onbeforeunload = undefined;
}
function onUpdateSuccess_xray(btn) {
alert(updateSuccessText);
if (btn) {
btn.value = updateSuccessText;
btn.placeholder = updateSuccessText;
btn.disabled = true;
}
window.setTimeout(function () {
window.location.reload();
}, 1000);
}
function onRequestError_xray(btn, errorMessage) {
btn.disabled = false;
btn.value = manuallyUpdateText;
if (errorMessage) {
alert(errorMessage);
}
}
function onBtnClick_xray(btn) {
if (xrayInfo === undefined) {
checkUpdate_xray(btn);
} else {
doUpdate_xray(btn);
}
}
function checkUpdate_xray(btn) {
btn.disabled = true;
btn.value = inProgressText;
addPageNotice_xray();
var ckeckDetailElm = document.getElementById(btn.id + '-detail');
XHR.get('<%=api.url("xray_check")%>', {
token: tokenStr,
arch: ''
}, function (x, json) {
removePageNotice_xray();
if (json.code) {
xrayInfo = undefined;
onRequestError_xray(btn, json.error);
} else {
if (json.has_update) {
xrayInfo = 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_xray(btn) {
btn.disabled = true;
btn.value = downloadingText;
addPageNotice_xray();
var xrayUpdateUrl = '<%=api.url("xray_update")%>';
// Download file
XHR.get(xrayUpdateUrl, {
token: tokenStr,
url: xrayInfo ? xrayInfo.data.browser_download_url : '',
size: xrayInfo ? xrayInfo.data.size / 1024 : null
}, function (x, json) {
if (json.code) {
removePageNotice_xray();
onRequestError_xray(btn, json.error);
} else {
btn.value = decompressioningText;
// Extract file
XHR.get(xrayUpdateUrl, {
token: tokenStr,
task: 'extract',
file: json.file,
subfix: xrayInfo ? xrayInfo.type : ''
}, function (x, json) {
if (json.code) {
removePageNotice_xray();
onRequestError_xray(btn, json.error);
} else {
btn.value = movingText;
// Move file to target dir
XHR.get(xrayUpdateUrl, {
token: tokenStr,
task: 'move',
file: json.file
}, function (x, json) {
removePageNotice_xray();
if (json.code) {
onRequestError_xray(btn, json.error);
} else {
onUpdateSuccess_xray(btn);
}
}, 300)
}
}, 300)
}
}, 300)
}
//]]>
</script>
<div class="cbi-value">
<label class="cbi-value-title">Xray
<%:Version%>
</label>
<div class="cbi-value-field">
<div class="cbi-value-description">
<span><%=xray_version ~="" and xray_version or translate("Null") %> 】</span>
<input class="btn cbi-button cbi-button-apply" type="button" id="_xray-check_btn"
onclick="onBtnClick_xray(this);" value="<%:Manually update%>" />
<span id="_xray-check_btn-detail"></span>
</div>
</div>
</div>

View File

@ -0,0 +1,22 @@
<%
local api = require "luci.model.cbi.passwall.api.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%>" />

View File

@ -0,0 +1,43 @@
<%
local api = require "luci.model.cbi.passwall.api.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, especially the GFW mode. 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>
<li style="color:red">4. <span><%:If you have a wrong DNS process, the consequences are at your own risk!%></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 += "<%:You can use load balancing for failover.%>" + "<br />";
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>

View File

@ -0,0 +1,143 @@
<%
local api = require "luci.model.cbi.passwall.api.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>&nbsp';
var div2 = '<font style="font-weight:bold;" color="red">X</font>&nbsp';
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-passwall-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++) {
var dom = node.childNodes[i].childNodes[k];
if (dom.id) {
var s = dom.id.match(reg1);
if (s) {
dom_id = dom.id.split("cbi-").join("cbid-").split("-").join(".");
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("_default") != 0 && node_select_value.indexOf("_direct") != 0 && node_select_value.indexOf("_blackhole") != 0) {
if (global_id != null && node_select_value.indexOf("tcp") == 0) {
var d = global_id + "-tcp_node";
d = d.replace("cbi-", "cbid-").replace(new RegExp("-", 'g'), ".");
var dom = document.getElementsByName(d)[0];
var _node_select_value = dom.value;
if (_node_select_value && _node_select_value != "nil") {
node_select_value = _node_select_value;
}
}
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_html = "";
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 + "'");
new_html = new_a.outerHTML;
if (s[0] == "tcp" || s[0] == "udp") {
var log_a = document.createElement("a");
log_a.innerHTML = "<%:Log%>";
log_a.href = "#";
log_a.setAttribute("onclick", "window.open('" + '<%=api.url("get_redir_log")%>' + "?proto=" + s[0] + "', '_blank')");
new_html += "&nbsp&nbsp" + log_a.outerHTML;
}
node_select.insertAdjacentHTML("afterend", "&nbsp&nbsp" + new_html);
}
}
}
}
}
}
}
var socks = document.getElementById("cbi-passwall-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++) {
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") {
if (global_id != null && node_select_value.indexOf("tcp") == 0) {
var d = global_id + "-tcp_node";
d = d.replace("cbi-", "cbid-").replace(new RegExp("-", 'g'), ".");
var dom = document.getElementsByName(d)[0];
var _node_select_value = dom.value;
if (_node_select_value && _node_select_value != "nil") {
node_select_value = _node_select_value;
}
}
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", "&nbsp&nbsp" + new_a.outerHTML);
}
}
}
}
setTimeout("go()", 1000);
//]]>
</script>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,26 @@
<%
local api = require "luci.model.cbi.passwall.api.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>

View File

@ -0,0 +1,30 @@
<%
local api = require "luci.model.cbi.passwall.api.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>

View File

@ -0,0 +1,108 @@
<%
local api = require "luci.model.cbi.passwall.api.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 trojan-go 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>

View File

@ -0,0 +1,960 @@
<%+cbi/valueheader%>
<%
local api = require "luci.model.cbi.passwall.api.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) {
var m = url.match(/^(([^:\/?#]+:)?(?:\/\/((?:([^\/?#:]*)([^\/?#:]*)@)?([^\/?#:]*)(?::([^\/?#:]*))?)))?([^?#]*)(\?[^#]*)?(#.*)?$/),
r = {
hash: m[10] || "", // #asd
host: m[3] || "", // localhost:257
hostname: m[6] || "", // localhost
href: m[0] || "", // http://username:password@localhost:257/deploy/?asd=asd#asd
origin: m[1] || "", // http://username:password@localhost:257
pathname: m[8] || (m[1] ? "/" : ""), // /deploy/
port: m[7] || "", // 257
protocol: m[2] || "", // http:
search: m[9] || "", // ?asd=asd
passwd: m[4] || "", // username
removed: m[5] || "" // password
};
if (r.protocol.length === 2) {
r.protocol = "file:///" + r.protocol.toUpperCase();
r.origin = r.protocol + "//" + r.host;
}
r.href = r.origin + r.pathname + r.search + r.hash;
return m && r;
}
function buildUrl(btn, urlname, sid) {
var opt = {
base: "cbid.passwall",
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 url = null;
if (v_type === "SS") {
var v_server = opt.get("address");
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) + "@" +
v_server.value + ":" +
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_server = opt.get("address");
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 = v_server.value + ":" +
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.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;
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_server = opt.get("address");
var v_port = opt.get("port");
url = encodeURIComponent(v_password.value) +
"@" + v_server.value +
":" + 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") {
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 += "&type=" + v_transport;
params += opt.query("encryption", "encryption");
if (opt.get("tls").checked) {
var v_security = "tls";
if (opt.get("xtls").checked) {
v_security = "xtls";
var v_flow = "xtls-rprx-direct";
if (opt.get("flow").value) {
v_flow = opt.get("flow").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") || v_type === "Trojan" || v_type === "Trojan-Plus" || v_type === "Trojan-Go") {
if (((v_type === "V2ray" || v_type === "Xray") && opt.get("protocol").value === "trojan") || v_type === "Trojan-Plus") {
v_type = "trojan";
}
var v_password = opt.get(!opt.client && v_type === "Trojan-Go" ? "passwords" : "password");
var v_server = opt.get("address");
var v_port = opt.get("port");
url = encodeURIComponent(v_password.value) +
"@" + v_server.value +
":" + v_port.value + "/?";
var params = "";
if (opt.get("tls").checked) {
params += opt.query("sni", "tls_serverName");
if (v_type !== "Trojan-Go") {
params += "&tls=1"
params += opt.query("allowinsecure", "tls_allowInsecure");
}
}
if (v_type === "Trojan-Go") {
if (!opt.get("tls").checked && opt.get("trojan_transport").value === "original") {
var plugin = {};
plugin.type = opt.get("plugin_type").value;
if (plugin.type !== "plaintext") {
plugin.command = opt.get("plugin_cmd").value;
plugin.option = opt.get("plugin_option").value;
plugin.arg = opt.getlist("plugin_arg");
}
params += "&plugin=" + encodeURIComponent(JSON.stringify(plugin));
}
params += opt.query("type", "trojan_transport");
var ws = (opt.get("trojan_transport").value.indexOf("ws") !== -1);
var h2 = (opt.get("trojan_transport").value.indexOf("h2") !== -1);
if (ws) {
params += opt.query("host", "ws_host");
params += opt.query("path", "ws_path");
} else if (h2) {
params += opt.query("host", "h2_host");
params += opt.query("path", "h2_path");
}
var enc = "none";
if (opt.get("ss_aead").checked === true) {
enc = "ss;" +
opt.get("ss_aead_method").value +
":" + opt.get("ss_aead_pwd").value;
}
params += "&encryption=" + encodeURIComponent(enc);
}
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_server = opt.get("address");
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 + v_server.value + ":" + v_port.value + v_path_value);
} else {
params += "&" + url_protocol + "=" + encodeURIComponent(v_server.value + ":" + v_port.value);
}
url += url_protocol;
url += params;
} else if (v_type === "Hysteria") {
var v_server = opt.get("address");
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 =
v_server.value + ":" +
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.passwall',
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" || ssu[0] === "trojan-plus") {
var stype = "Trojan-Plus";
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 (queryParam.mux || queryParam.ws || queryParam.h2 || queryParam.ss || queryParam.plugin) {
stype = "Trojan-Go";
}
opt.set('type', stype);
opt.set('address', m.hostname);
opt.set('port', m.port || "443");
opt.set(!opt.client && stype === "Trojan-Go" ? 'passwords' : 'password', decodeURIComponent(password));
var tls = true;
if (stype === "Trojan-Go") {
tls = queryParam.plugin === undefined;
}
if (tls === false) { alert("TODO: plugin params for trojan-go."); }
opt.set('tls', tls);
if (tls) {
opt.set('tls_serverName', queryParam.peer || queryParam.sni || '');
opt.set('tls_allowInsecure', queryParam.allowinsecure === '1');
}
if (stype === "Trojan-Go") {
var tran = 'original';
var ws = null;
var h2 = null;
if (queryParam.type) {
ws = queryParam.type.indexOf('ws') !== -1;
h2 = queryParam.type.indexOf('h2') !== -1;
}
if (ws && h2) {
tran = 'h2+ws'
} else {
if (ws) tran = 'ws';
if (h2) tran = 'h2';
}
opt.set('trojan_transport', 'tran');
if (ws) {
opt.set('ws_host', queryParam.wshost || '');
opt.set('ws_path', queryParam.wspath || '/');
}
if (h2) {
opt.set('h2_host', queryParam.h2host || '');
opt.set('h2_path', queryParam.h2path || '/');
}
var ss = queryParam.ss === '1';
opt.set('ss_aead', ss);
if (ss) {
opt.set('ss_aead_method', queryParam.ssmethod.toLowerCase() || '');
opt.set('ss_aead_pwd', queryParam.sspasswd || '');
}
}
opt.set('mux', queryParam.mux === '1');
if (m.hash) {
opt.set('remarks', decodeURI(m.hash.substr(1)));
}
} else if (ssu[0] === "trojan-go") {
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('&');
for (i = 0; i < queryArray.length; i++) {
var params = queryArray[i].split('=');
queryParam[decodeURIComponent(params[0]).toLowerCase()] = decodeURIComponent(params[1] || '');
}
}
opt.set('type', 'Trojan-Go');
opt.set('address', m.hostname);
opt.set('port', m.port || "443");
opt.set(opt.client ? 'password' : 'passwords', decodeURIComponent(password));
opt.set('tls', '1');
opt.set('tls_allowInsecure', '0');
opt.set('tls_serverName', queryParam.peer || queryParam.sni || '');
var plugin = queryParam.plugin !== undefined;
if (plugin) {
opt.set('trojan_transport', 'original');
var plugin = JSON.parse(queryParam.plugin);
if (plugin) {
opt.set('plugin_type', plugin.type);
if (plugin.type !== "plaintext") {
opt.set('plugin_cmd', plugin.command);
opt.set('plugin_option', plugin.option);
opt.setlist('plugin_arg', plugin.arg);
}
} else
alert(queryParam.plugin);
}
var tran = 'original';
var or = queryParam.type === undefined || queryParam.type === 'original';
var ws = null;
var h2 = null;
if (queryParam.type) {
ws = queryParam.type.indexOf('ws') !== -1;
h2 = queryParam.type.indexOf('h2') !== -1;
}
if (ws && h2) {
tran = 'h2+ws'
} else {
if (ws) tran = 'ws';
if (h2) tran = 'h2';
}
opt.set('trojan_transport', tran);
if (ws) {
opt.set('ws_host', queryParam.host || '');
opt.set('ws_path', queryParam.path || '/');
}
if (h2){
opt.set('h2_host', queryParam.host || '');
opt.set('h2_path', queryParam.path || '/');
}
var enc = {};
var ss = false;
if (queryParam.encryption) {
var r = queryParam.encryption.match(/^(ss);([^;:]*)[;:](.*)$/),
enc = {type: r[1], method: r[2], password: r[3]};
}
ss = enc.type === 'ss';
opt.set('ss_aead', ss);
if (ss) {
opt.set('ss_aead_method', enc.method.toLowerCase() || '');
opt.set('ss_aead_pwd', enc.password || '');
}
opt.set('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" || queryParam.security == "xtls") {
opt.set('tls', true);
if (queryParam.security == "xtls") {
opt.set('xtls', true);
opt.set('flow', queryParam.flow || "xtls-rprx-direct");
}
opt.set('tls_serverName', queryParam.sni || '');
opt.set('tls_allowInsecure', true);
if (queryParam.allowinsecure === '0') {
opt.set('tls_allowInsecure', false);
}
}
}
queryParam.type = queryParam.type.toLowerCase();
if (queryParam.type === "kcp" || queryParam.type === "mkcp")
queryParam.type = "mkcp"
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") {
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) || "");
}
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;
}
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%>

View File

@ -0,0 +1,475 @@
<%
local api = require "luci.model.cbi.passwall.api.api"
-%>
<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: #9f9f9f38 !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.passwall." + 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-passwall-" + id);
if (dom) {
var trs = document.getElementById("cbi-passwall-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.passwall.nodes');
}
}
}
}
}
function checked_all_node(btn) {
var doms = document.getElementById("cbi-passwall-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-passwall-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-passwall-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-passwall-" + 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(protocol) {
if (confirm('<%:Are you sure set to%> ' + protocol.toUpperCase() + '<%:the server?%>')==true){
window.location.href = '<%=api.url("set_node")%>?protocol=' + protocol + '&section=' + section;
}
}
function get_address_full(id) {
try {
var address = document.getElementById("cbid.passwall." + id + ".address").value;
var port = document.getElementById("cbid.passwall." + 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-passwall-" + id);
if (dom) {
dom.classList.add("_now_use");
dom.title = "当前TCP节点";
//var v = "<a style='color: red'>当前TCP节点</a>" + document.getElementById("cbid.passwall." + id + ".remarks").value;
//document.getElementById("cbi-passwall-" + id + "-remarks").innerHTML = v;
}
}
id = result["UDP"];
if (id) {
var dom = document.getElementById("cbi-passwall-" + 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-passwall-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 + '" />&nbsp;&nbsp;';
//添加"置顶"按钮
new_div += '<input class="btn cbi-button" type="button" value="<%:To Top%>" onclick="_cbi_row_top(\'' + id + '\')"/>&nbsp;&nbsp;';
//添加"应用"按钮
new_div += '<input class="btn cbi-button cbi-button-apply" type="button" value="<%:Use%>" id="apply_' + id + '" onclick="open_set_node_div(\'' + id + '\')"/>&nbsp;&nbsp;';
//添加"复制"按钮
new_div += '<input class="btn cbi-button cbi-button-add" type="button" value="<%:Copy%>" onclick="copy_node(\'' + id + '\')"/>&nbsp;&nbsp;';
td.innerHTML = new_div + td.innerHTML;
var obj = {};
obj.id = id;
obj.type = document.getElementById("cbid.passwall." + id + ".type").value;
var address_dom = document.getElementById("cbid.passwall." + id + ".address");
var port_dom = document.getElementById("cbid.passwall." + 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.passwall." + 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>&nbsp&nbsp&nbsp";
}
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">
<input class="btn cbi-button cbi-button-edit" type="button" onclick="set_node('tcp')" value="TCP" />
<input class="btn cbi-button cbi-button-edit" type="button" onclick="set_node('udp')" value="UDP" />
<input class="btn cbi-button cbi-button-remove" type="button" onclick="close_set_node_div()" value="<%:Close%>" />
</div>
</div>
</div>

View File

@ -0,0 +1,76 @@
<%
local api = require "luci.model.cbi.passwall.api.api"
local gfwlist_update = api.uci_get_type("global_rules", "gfwlist_update", "1") == "1" and "checked='checked'" or ""
local chnroute_update = api.uci_get_type("global_rules", "chnroute_update", "1") == "1" and "checked='checked'" or ""
local chnroute6_update = api.uci_get_type("global_rules", "chnroute6_update", "1") == "1" and "checked='checked'" or ""
local chnlist_update = api.uci_get_type("global_rules", "chnlist_update", "1") == "1" and "checked='checked'" or ""
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
},
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="gfwlist" value="1" <%=gfwlist_update%> />
gfwlist
</label>
<label>
<input class="cbi-input-checkbox" type="checkbox" name="chnroute" value="1" <%=chnroute_update%> />
chnroute
</label>
<label>
<input class="cbi-input-checkbox" type="checkbox" name="chnroute6" value="1" <%=chnroute6_update%> />
chnroute6
</label>
<label>
<input class="cbi-input-checkbox" type="checkbox" name="chnlist" value="1" <%=chnlist_update%> />
chnlist
</label>
<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>

View File

@ -0,0 +1,34 @@
<%
local api = require "luci.model.cbi.passwall.api.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>

View File

@ -0,0 +1,38 @@
<%
local api = require "luci.model.cbi.passwall.api.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-passwall_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\')"/>&nbsp;&nbsp;';
td.innerHTML = new_div + td.innerHTML;
}
catch(err) {
console.error(err);
}
}
//]]>
</script>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
zh-cn

View File

@ -0,0 +1,4 @@
config global 'global'
option enable '0'

View File

@ -0,0 +1,80 @@
#!/bin/sh /etc/rc.common
START=99
STOP=15
CONFIG=passwall
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() {
$APP_FILE boot
}
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
}
disable() {
rm -f "$IPKG_INSTROOT"/etc/rc.d/S??zzz_${CONFIG}
rm -f "$IPKG_INSTROOT"/etc/rc.d/K??zzz_${CONFIG}
}
enable() {
err=1
[ "$START" ] && \
ln -sf "../init.d/${CONFIG}" "$IPKG_INSTROOT/etc/rc.d/S${START}zzz_${CONFIG}" && \
err=0
[ "$STOP" ] && \
ln -sf "../init.d/${CONFIG}" "$IPKG_INSTROOT/etc/rc.d/K${STOP}zzz_${CONFIG}" && \
err=0
return $err
}
enabled() {
[ -x "$IPKG_INSTROOT/etc/rc.d/S${START}zzz_${CONFIG}" ]
}

View File

@ -0,0 +1,16 @@
#!/bin/sh /etc/rc.common
START=99
start() {
lua /usr/lib/lua/luci/model/cbi/passwall/server/api/app.lua start
}
stop() {
lua /usr/lib/lua/luci/model/cbi/passwall/server/api/app.lua stop
}
restart() {
stop
start
}

View File

@ -0,0 +1,35 @@
#!/bin/sh
uci -q batch <<-EOF >/dev/null
set dhcp.@dnsmasq[0].localuse=1
commit dhcp
delete ucitrack.@passwall[-1]
add ucitrack passwall
set ucitrack.@passwall[-1].init=passwall
commit ucitrack
delete firewall.passwall
set firewall.passwall=include
set firewall.passwall.type=script
set firewall.passwall.path=/var/etc/passwall.include
set firewall.passwall.reload=1
commit firewall
delete ucitrack.@passwall_server[-1]
add ucitrack passwall_server
set ucitrack.@passwall_server[-1].init=passwall_server
commit ucitrack
delete firewall.passwall_server
set firewall.passwall_server=include
set firewall.passwall_server.type=script
set firewall.passwall_server.path=/var/etc/passwall_server.include
set firewall.passwall_server.reload=1
commit firewall
set uhttpd.main.max_requests=50
commit uhttpd
EOF
[ ! -s "/etc/config/passwall" ] && cp -f /usr/share/passwall/0_default_config /etc/config/passwall
touch /etc/config/passwall_show >/dev/null 2>&1
rm -f /tmp/luci-indexcache
rm -rf /tmp/luci-modulecache/
killall -HUP rpcd 2>/dev/null
exit 0

View File

@ -0,0 +1,156 @@
config global
option enabled '0'
option socks_enabled '0'
option tcp_node 'nil'
option udp_node 'nil'
option dns_mode 'pdnsd'
option dns_forward '1.1.1.1'
option filter_proxy_ipv6 '0'
option tcp_proxy_mode 'chnroute'
option udp_proxy_mode 'chnroute'
option localhost_tcp_proxy_mode 'default'
option localhost_udp_proxy_mode 'default'
option close_log_tcp '0'
option close_log_udp '0'
option loglevel 'error'
option trojan_loglevel '4'
config global_haproxy
option balancing_enable '0'
config global_delay
option auto_on '0'
option start_daemon '1'
option start_delay '1'
config global_forwarding
option process '0'
option tcp_no_redir_ports 'disable'
option udp_no_redir_ports '53'
option tcp_proxy_drop_ports 'disable'
option udp_proxy_drop_ports '80,443'
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 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 chnlist_update '1'
option chnroute_update '1'
option chnroute6_update '1'
option gfwlist_update '1'
option geosite_update '0'
option geoip_update '0'
list gfwlist_url 'https://raw.cooluc.com/YW5vbnltb3Vz/domain-list-community/release/gfwlist.txt'
list chnroute_url 'https://ispip.clang.cn/all_cn.txt'
list chnroute6_url 'https://ispip.clang.cn/all_cn_ipv6.txt'
list chnlist_url 'https://raw.cooluc.com/felixonmars/dnsmasq-china-list/master/accelerated-domains.china.conf'
list chnlist_url 'https://raw.cooluc.com/felixonmars/dnsmasq-china-list/master/apple.china.conf'
list chnlist_url 'https://raw.cooluc.com/felixonmars/dnsmasq-china-list/master/google.china.conf'
option v2ray_location_asset '/usr/share/v2ray/'
config global_app
option v2ray_file '/usr/bin/v2ray'
option xray_file '/usr/bin/xray'
option trojan_go_file '/usr/bin/trojan-go'
option brook_file '/usr/bin/brook'
option hysteria_file '/usr/bin/hysteria'
config global_subscribe
option subscribe_proxy '0'
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 AD 'nil'
option BT '_direct'
option Telegram 'nil'
option Netflix 'nil'
option TVB 'nil'
option Cloudflare 'nil'
option Github 'nil'
option Apple 'nil'
option China 'nil'
option default_node 'nil'
option domainStrategy 'IPOnDemand'
config shunt_rules 'AD'
option remarks 'AD'
option domain_list 'geosite:category-ads-all'
config shunt_rules 'BT'
option remarks 'BT'
option protocol 'bittorrent'
config shunt_rules 'Telegram'
option remarks 'Telegram'
option ip_list 'geoip:telegram'
config shunt_rules 'Netflix'
option remarks 'Netflix'
option domain_list 'fast.com
netflix
netflix.com
netflix.net
nflxso.net
nflxext.com
nflximg.com
nflximg.net
nflxvideo.net
netflixdnstest0.com
netflixdnstest1.com
netflixdnstest2.com
netflixdnstest3.com
netflixdnstest4.com
netflixdnstest5.com
netflixdnstest6.com
netflixdnstest7.com
netflixdnstest8.com
netflixdnstest9.com'
option ip_list 'geoip:netflix'
config shunt_rules 'TVB'
option remarks 'TVB'
option domain_list 'tvb.com
mytvsuper.com'
config shunt_rules 'Github'
option remarks 'Github'
option domain_list 'github.com
github.io
githubusercontent.com'
config shunt_rules 'Cloudflare'
option remarks 'Cloudflare'
option ip_list 'geoip:cloudflare'
config shunt_rules 'Apple'
option remarks 'Apple'
option domain_list 'geosite:apple-cn'
option ip_list '17.0.0.0/8'
config shunt_rules 'China'
option remarks 'China'
option domain_list 'geosite:cn'
option ip_list 'geoip:cn'

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,98 @@
#!/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}
}
add() {
local FLAG TMP_DNSMASQ_PATH DNSMASQ_CONF_FILE DEFAULT_DNS LOCAL_DNS TUN_DNS REMOTE_FAKEDNS CHINADNS_DNS TCP_NODE PROXY_MODE NO_PROXY_IPV6 NO_LOGIC_LOG
eval_set_val $@
lua $APP_PATH/helper_dnsmasq_add.lua -FLAG $FLAG -TMP_DNSMASQ_PATH $TMP_DNSMASQ_PATH -DNSMASQ_CONF_FILE $DNSMASQ_CONF_FILE -DEFAULT_DNS $DEFAULT_DNS -LOCAL_DNS $LOCAL_DNS -TUN_DNS $TUN_DNS -REMOTE_FAKEDNS ${REMOTE_FAKEDNS:-0} -CHINADNS_DNS ${CHINADNS_DNS:-0} -TCP_NODE $TCP_NODE -PROXY_MODE $PROXY_MODE -NO_PROXY_IPV6 ${NO_PROXY_IPV6:-0} -NO_LOGIC_LOG ${NO_LOGIC_LOG:-0}
}
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

View File

@ -0,0 +1,430 @@
local api = require "luci.model.cbi.passwall.api.api"
local var = api.get_args(arg)
local FLAG = var["-FLAG"]
local TMP_DNSMASQ_PATH = var["-TMP_DNSMASQ_PATH"]
local DNSMASQ_CONF_FILE = var["-DNSMASQ_CONF_FILE"]
local DEFAULT_DNS = var["-DEFAULT_DNS"]
local LOCAL_DNS = var["-LOCAL_DNS"]
local TUN_DNS = var["-TUN_DNS"]
local REMOTE_FAKEDNS = var["-REMOTE_FAKEDNS"]
local CHINADNS_DNS = var["-CHINADNS_DNS"]
local TCP_NODE = var["-TCP_NODE"]
local PROXY_MODE = var["-PROXY_MODE"]
local NO_PROXY_IPV6 = var["-NO_PROXY_IPV6"]
local NO_LOGIC_LOG = var["-NO_LOGIC_LOG"]
local LOG_FILE = "/tmp/log/passwall.log"
local CACHE_PATH = "/tmp/etc/passwall_tmp"
local CACHE_FLAG = "dns_" .. FLAG
local CACHE_DNS_PATH = CACHE_PATH .. "/" .. CACHE_FLAG
local CACHE_MD5_FILE = CACHE_DNS_PATH .. ".md5"
local uci = api.uci
local sys = api.sys
local jsonc = api.jsonc
local appname = api.appname
local fs = api.fs
local datatypes = api.datatypes
local list1 = {}
local excluded_domain = {}
local excluded_domain_str = "!"
local function log(...)
if NO_LOGIC_LOG == "1" then
return
end
local f, err = io.open(LOG_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
--从url获取域名
local function get_domain_from_url(url)
if url then
if datatypes.hostname(url) then
return url
end
local domain = url:match("//([^/]+)")
if domain then
return domain
end
end
return ""
end
local function check_dns(domain, dns)
if domain == "" or domain:find("#") then
return false
end
if not dns then
return
end
for k,v in ipairs(list1[domain].dns) do
if dns == v then
return true
end
end
return false
end
local function check_ipset(domain, ipset)
if domain == "" or domain:find("#") then
return false
end
if not ipset then
return
end
for k,v in ipairs(list1[domain].ipsets) do
if ipset == v then
return true
end
end
return false
end
local function set_domain_address(domain, address)
if domain == "" or domain:find("#") then
return
end
if not list1[domain] then
list1[domain] = {
dns = {},
ipsets = {}
}
end
if not list1[domain].address then
list1[domain].address = address
end
end
local function set_domain_dns(domain, dns)
if domain == "" or domain:find("#") then
return
end
if not dns then
return
end
if not list1[domain] then
list1[domain] = {
dns = {},
ipsets = {}
}
end
for line in string.gmatch(dns, '[^' .. "," .. ']+') do
if not check_dns(domain, line) then
table.insert(list1[domain].dns, line)
end
end
end
local function set_domain_ipset(domain, ipset)
if domain == "" or domain:find("#") then
return
end
if not ipset then
return
end
if not list1[domain] then
list1[domain] = {
dns = {},
ipsets = {}
}
end
for line in string.gmatch(ipset, '[^' .. "," .. ']+') do
if not check_ipset(domain, line) then
table.insert(list1[domain].ipsets, line)
end
end
end
local function add_excluded_domain(domain)
if domain == "" or domain:find("#") then
return
end
table.insert(excluded_domain, domain)
excluded_domain_str = excluded_domain_str .. "|" .. domain
end
local function check_excluded_domain(domain)
if domain == "" or domain:find("#") then
return false
end
for k,v in ipairs(excluded_domain) do
if domain:find(v) then
return true
end
end
return false
end
local dnsmasq_default_dns
local cache_md5 = ""
local str = TMP_DNSMASQ_PATH .. DNSMASQ_CONF_FILE .. DEFAULT_DNS .. LOCAL_DNS .. TUN_DNS .. REMOTE_FAKEDNS .. CHINADNS_DNS .. PROXY_MODE .. NO_PROXY_IPV6
local md5 = luci.sys.exec("echo -n $(echo '" .. str .. "' | md5sum | awk '{print $1}')")
if fs.access(CACHE_MD5_FILE) then
for line in io.lines(CACHE_MD5_FILE) do
cache_md5 = line
end
end
if cache_md5 ~= md5 then
sys.call("rm -rf " .. CACHE_PATH .. "/" .. CACHE_FLAG .. "*")
end
local global = PROXY_MODE:find("global")
local returnhome = PROXY_MODE:find("returnhome")
local chnlist = PROXY_MODE:find("chnroute")
local gfwlist = PROXY_MODE:find("gfwlist")
local only_global
if CHINADNS_DNS ~= "0" then
dnsmasq_default_dns = CHINADNS_DNS
end
if global and (not returnhome and not chnlist and not gfwlist) then
dnsmasq_default_dns = TUN_DNS
only_global = 1
end
if not fs.access(CACHE_DNS_PATH) then
fs.mkdir("/tmp/dnsmasq.d")
fs.mkdir(CACHE_DNS_PATH)
--屏蔽列表
for line in io.lines("/usr/share/passwall/rules/block_host") do
if line ~= "" and not line:find("#") then
set_domain_address(line, "0.0.0.0")
end
end
--始终用国内DNS解析节点域名
uci:foreach(appname, "nodes", function(t)
local address = t.address
if datatypes.hostname(address) then
set_domain_dns(address, LOCAL_DNS)
set_domain_ipset(address, "vpsiplist,vpsiplist6")
end
end)
log(string.format(" - 节点列表中的域名(vpsiplist)%s", LOCAL_DNS or "默认"))
--始终用国内DNS解析直连白名单列表
for line in io.lines("/usr/share/passwall/rules/direct_host") do
if line ~= "" and not line:find("#") then
add_excluded_domain(line)
set_domain_dns(line, LOCAL_DNS)
set_domain_ipset(line, "whitelist,whitelist6")
end
end
log(string.format(" - 域名白名单(whitelist)%s", LOCAL_DNS or "默认"))
local fwd_dns = LOCAL_DNS
local ipset_flag = "whitelist,whitelist6"
local no_ipv6
if uci:get(appname, "@global_subscribe[0]", "subscribe_proxy") or "0" == "1" then
fwd_dns = TUN_DNS
ipset_flag = "blacklist,blacklist6"
if NO_PROXY_IPV6 == "1" then
ipset_flag = "blacklist"
no_ipv6 = true
end
if not only_global then
if REMOTE_FAKEDNS == "1" then
ipset_flag = nil
end
end
end
uci:foreach(appname, "subscribe_list", function(t)
local domain = get_domain_from_url(t.url)
if domain then
if no_ipv6 then
set_domain_address(domain, "::")
end
set_domain_dns(domain, fwd_dns)
set_domain_ipset(domain, ipset_flag)
end
end)
log(string.format(" - 节点订阅域名(blacklist)%s", fwd_dns or "默认"))
--始终使用远程DNS解析代理黑名单列表
for line in io.lines("/usr/share/passwall/rules/proxy_host") do
if line ~= "" and not line:find("#") then
add_excluded_domain(line)
local ipset_flag = "blacklist,blacklist6"
if NO_PROXY_IPV6 == "1" then
set_domain_address(line, "::")
ipset_flag = "blacklist"
end
if REMOTE_FAKEDNS == "1" then
ipset_flag = nil
end
set_domain_dns(line, TUN_DNS)
set_domain_ipset(line, ipset_flag)
end
end
log(string.format(" - 代理域名表(blacklist)%s", TUN_DNS or "默认"))
--分流规则
if uci:get(appname, TCP_NODE, "protocol") == "_shunt" then
local t = uci:get_all(appname, TCP_NODE)
local default_node_id = t["default_node"] or "_direct"
uci:foreach(appname, "shunt_rules", function(s)
local _node_id = t[s[".name"]] or "nil"
if _node_id ~= "nil" and _node_id ~= "_blackhole" then
if _node_id == "_default" then
_node_id = default_node_id
end
fwd_dns = nil
ipset_flag = nil
no_ipv6 = nil
if _node_id == "_direct" then
fwd_dns = LOCAL_DNS
ipset_flag = "whitelist,whitelist6"
else
fwd_dns = TUN_DNS
ipset_flag = "shuntlist,shuntlist6"
if NO_PROXY_IPV6 == "1" then
ipset_flag = "shuntlist"
no_ipv6 = true
end
if not only_global then
if REMOTE_FAKEDNS == "1" then
ipset_flag = nil
end
end
end
local domain_list = s.domain_list or ""
for line in string.gmatch(domain_list, "[^\r\n]+") do
if line ~= "" and not line:find("#") and not line:find("regexp:") and not line:find("geosite:") and not line:find("ext:") then
if line:find("domain:") or line:find("full:") then
line = string.match(line, ":([^:]+)$")
end
add_excluded_domain(line)
if no_ipv6 then
set_domain_address(line, "::")
end
set_domain_dns(line, fwd_dns)
set_domain_ipset(line, ipset_flag)
end
end
if _node_id ~= "_direct" then
log(string.format(" - V2ray/Xray分流规则(%s)%s", s.remarks, fwd_dns or "默认"))
end
end
end)
end
--如果没有使用回国模式
if not returnhome then
if fs.access("/usr/share/passwall/rules/gfwlist") then
local gfwlist_str = sys.exec('cat /usr/share/passwall/rules/gfwlist | grep -v -E "^#" | grep -v -E "' .. excluded_domain_str .. '"')
for line in string.gmatch(gfwlist_str, "[^\r\n]+") do
if line ~= "" then
local ipset_flag = "gfwlist,gfwlist6"
if NO_PROXY_IPV6 == "1" then
ipset_flag = "gfwlist"
set_domain_address(line, "::")
end
if not only_global then
fwd_dns = TUN_DNS
if CHINADNS_DNS ~= "0" then
fwd_dns = nil
end
if REMOTE_FAKEDNS == "1" then
ipset_flag = nil
end
set_domain_dns(line, fwd_dns)
set_domain_ipset(line, ipset_flag)
end
end
end
log(string.format(" - 防火墙域名表(gfwlist)%s", fwd_dns or "默认"))
end
if CHINADNS_DNS ~= "0" then
if fs.access("/usr/share/passwall/rules/chnlist") then
fwd_dns = nil
local chnlist_str = sys.exec('cat /usr/share/passwall/rules/chnlist | grep -v -E "^#" | grep -v -E "' .. excluded_domain_str .. '"')
for line in string.gmatch(chnlist_str, "[^\r\n]+") do
if line ~= "" then
set_domain_dns(line, fwd_dns)
set_domain_ipset(line, "chnroute,chnroute6")
end
end
end
log(string.format(" - 中国域名表(chnroute)%s", fwd_dns or "默认"))
end
else
if fs.access("/usr/share/passwall/rules/chnlist") then
local chnlist_str = sys.exec('cat /usr/share/passwall/rules/chnlist | grep -v -E "^#" | grep -v -E "' .. excluded_domain_str .. '"')
for line in string.gmatch(chnlist_str, "[^\r\n]+") do
if line ~= "" then
local ipset_flag = "chnroute,chnroute6"
if NO_PROXY_IPV6 == "1" then
ipset_flag = "chnroute"
set_domain_address(line, "::")
end
if not only_global then
set_domain_dns(line, TUN_DNS)
if REMOTE_FAKEDNS == "1" then
ipset_flag = nil
end
set_domain_ipset(line, ipset_flag)
end
end
end
log(string.format(" - 中国域名表(chnroute)%s", TUN_DNS or "默认"))
end
end
local address_out = io.open(CACHE_DNS_PATH .. "/000-address.conf", "a")
local server_out = io.open(CACHE_DNS_PATH .. "/001-server.conf", "a")
local ipset_out = io.open(CACHE_DNS_PATH .. "/ipset.conf", "a")
for key, value in pairs(list1) do
if value.address and #value.address > 0 then
address_out:write(string.format("address=/.%s/%s\n", key, value.address))
end
if value.dns and #value.dns > 0 then
for i, dns in ipairs(value.dns) do
server_out:write(string.format("server=/.%s/%s\n", key, dns))
end
end
if value.ipsets and #value.ipsets > 0 then
local ipsets_str = ""
for i, ipset in ipairs(value.ipsets) do
ipsets_str = ipsets_str .. ipset .. ","
end
ipsets_str = ipsets_str:sub(1, #ipsets_str - 1)
ipset_out:write(string.format("ipset=/.%s/%s\n", key, ipsets_str))
end
end
address_out:close()
server_out:close()
ipset_out:close()
local f_out = io.open(CACHE_MD5_FILE, "a")
f_out:write(md5)
f_out:close()
end
fs.symlink(CACHE_DNS_PATH, TMP_DNSMASQ_PATH)
local conf_out = io.open(DNSMASQ_CONF_FILE, "a")
conf_out:write(string.format("conf-dir=%s\n", TMP_DNSMASQ_PATH))
if dnsmasq_default_dns then
local f_out = io.open("/tmp/etc/passwall/default_DNS", "a")
f_out:write(DEFAULT_DNS)
f_out:close()
conf_out:write(string.format("server=%s\n", dnsmasq_default_dns))
conf_out:write("all-servers\n")
conf_out:write("no-poll\n")
conf_out:write("no-resolv\n")
log(string.format(" - 以上所列以外及默认:%s", dnsmasq_default_dns))
end
conf_out:close()
log(" - PassWall必须依赖于Dnsmasq如果你自行配置了错误的DNS流程将会导致域名(直连/代理域名)分流失效!!!")

View File

@ -0,0 +1,39 @@
#!/bin/sh
restart() {
local no_log
eval_set_val $@
_LOG_FILE=$LOG_FILE
[ -n "$no_log" ] && LOG_FILE="/dev/null"
rm -rf /tmp/smartdns.cache
/etc/init.d/smartdns reload >/dev/null 2>&1 &
LOG_FILE=${_LOG_FILE}
}
add() {
local FLAG SMARTDNS_CONF LOCAL_GROUP REMOTE_GROUP REMOTE_FAKEDNS TUN_DNS TCP_NODE PROXY_MODE NO_PROXY_IPV6 NO_LOGIC_LOG
eval_set_val $@
lua $APP_PATH/helper_smartdns_add.lua -FLAG $FLAG -SMARTDNS_CONF $SMARTDNS_CONF -LOCAL_GROUP ${LOCAL_GROUP:-nil} -REMOTE_GROUP ${REMOTE_GROUP:-nil} -REMOTE_FAKEDNS ${REMOTE_FAKEDNS:-0} -TUN_DNS $TUN_DNS -TCP_NODE $TCP_NODE -PROXY_MODE $PROXY_MODE -NO_PROXY_IPV6 ${NO_PROXY_IPV6:-0} -NO_LOGIC_LOG ${NO_LOGIC_LOG:-0}
}
del() {
rm -rf /tmp/etc/smartdns/passwall.conf
sed -i "/passwall/d" /etc/smartdns/custom.conf >/dev/null 2>&1
rm -rf /tmp/smartdns.cache
/etc/init.d/smartdns reload >/dev/null 2>&1 &
}
arg1=$1
shift
case $arg1 in
add)
add $@
;;
del)
del $@
;;
restart)
restart $@
;;
*) ;;
esac

View File

@ -0,0 +1,394 @@
local api = require "luci.model.cbi.passwall.api.api"
local var = api.get_args(arg)
local FLAG = var["-FLAG"]
local SMARTDNS_CONF = var["-SMARTDNS_CONF"]
local LOCAL_GROUP = var["-LOCAL_GROUP"]
local REMOTE_GROUP = var["-REMOTE_GROUP"]
local REMOTE_FAKEDNS = var["-REMOTE_FAKEDNS"]
local TUN_DNS = var["-TUN_DNS"]
local TCP_NODE = var["-TCP_NODE"]
local PROXY_MODE = var["-PROXY_MODE"]
local NO_PROXY_IPV6 = var["-NO_PROXY_IPV6"]
local NO_LOGIC_LOG = var["-NO_LOGIC_LOG"]
local LOG_FILE = "/tmp/log/passwall.log"
local CACHE_PATH = "/tmp/etc/passwall_tmp"
local CACHE_FLAG = "dns_" .. FLAG
local CACHE_DNS_FILE = CACHE_PATH .. "/" .. CACHE_FLAG .. ".conf"
local CACHE_MD5_FILE = CACHE_PATH .. "/" .. CACHE_FLAG .. ".md5"
local SMARTDNS_PATH = "/tmp/etc/smartdns"
local uci = api.uci
local sys = api.sys
local jsonc = api.jsonc
local appname = api.appname
local fs = api.fs
local datatypes = api.datatypes
local list1 = {}
local excluded_domain = {}
local excluded_domain_str = "!"
local function log(...)
if NO_LOGIC_LOG == "1" then
return
end
local f, err = io.open(LOG_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
--从url获取域名
local function get_domain_from_url(url)
if url then
if datatypes.hostname(url) then
return url
end
local domain = url:match("//([^/]+)")
if domain then
return domain
end
end
return ""
end
local function check_ipset(domain, ipset)
if domain == "" or domain:find("#") then
return false
end
if not ipset then
return
end
for k,v in ipairs(list1[domain].ipsets) do
if ipset == v then
return true
end
end
return false
end
local function set_domain_address(domain, address)
if domain == "" or domain:find("#") then
return
end
if not list1[domain] then
list1[domain] = {
ipsets = {}
}
end
if not list1[domain].address then
list1[domain].address = address
end
end
local function set_domain_group(domain, group)
if domain == "" or domain:find("#") then
return
end
if not group then
return
end
if not list1[domain] then
list1[domain] = {
ipsets = {}
}
end
if not list1[domain].group then
list1[domain].group = group
if group == REMOTE_GROUP then
list1[domain].speed_check_mode = "none"
end
end
end
local function set_domain_ipset(domain, ipset)
if domain == "" or domain:find("#") then
return
end
if not ipset then
return
end
if not list1[domain] then
list1[domain] = {
ipsets = {}
}
end
for line in string.gmatch(ipset, '[^' .. "," .. ']+') do
if not check_ipset(domain, line) then
table.insert(list1[domain].ipsets, line)
end
end
end
local function add_excluded_domain(domain)
if domain == "" or domain:find("#") then
return
end
table.insert(excluded_domain, domain)
excluded_domain_str = excluded_domain_str .. "|" .. domain
end
local function check_excluded_domain(domain)
if domain == "" or domain:find("#") then
return false
end
for k,v in ipairs(excluded_domain) do
if domain:find(v) then
return true
end
end
return false
end
local cache_md5 = ""
local str = SMARTDNS_CONF .. LOCAL_GROUP .. REMOTE_GROUP .. REMOTE_FAKEDNS .. TUN_DNS .. PROXY_MODE .. NO_PROXY_IPV6
local md5 = luci.sys.exec("echo -n $(echo '" .. str .. "' | md5sum | awk '{print $1}')")
if fs.access(CACHE_MD5_FILE) then
for line in io.lines(CACHE_MD5_FILE) do
cache_md5 = line
end
end
if cache_md5 ~= md5 then
sys.call("rm -rf " .. CACHE_PATH .. "/" .. CACHE_FLAG .. "*")
end
local global = PROXY_MODE:find("global")
local returnhome = PROXY_MODE:find("returnhome")
local chnlist = PROXY_MODE:find("chnroute")
local gfwlist = PROXY_MODE:find("gfwlist")
if not REMOTE_GROUP or REMOTE_GROUP == "nil" then
REMOTE_GROUP = "passwall_proxy"
if TUN_DNS then
TUN_DNS = TUN_DNS:gsub("#", ":")
end
sys.call('sed -i "/passwall/d" /etc/smartdns/custom.conf >/dev/null 2>&1')
end
if not fs.access(CACHE_DNS_FILE) then
sys.call(string.format('echo "server %s -group %s -exclude-default-group" >> %s', TUN_DNS, REMOTE_GROUP, CACHE_DNS_FILE))
--屏蔽列表
for line in io.lines("/usr/share/passwall/rules/block_host") do
if line ~= "" and not line:find("#") then
set_domain_address(line, "-")
end
end
--始终用国内DNS解析节点域名
uci:foreach(appname, "nodes", function(t)
local address = t.address
if datatypes.hostname(address) then
set_domain_group(address, LOCAL_GROUP)
set_domain_ipset(address, "#4:vpsiplist,#6:vpsiplist6")
end
end)
log(string.format(" - 节点列表中的域名(vpsiplist)使用分组:%s", LOCAL_GROUP or "默认"))
--始终用国内DNS解析直连白名单列表
for line in io.lines("/usr/share/passwall/rules/direct_host") do
if line ~= "" and not line:find("#") then
add_excluded_domain(line)
set_domain_group(line, LOCAL_GROUP)
set_domain_ipset(line, "#4:whitelist,#6:whitelist6")
end
end
log(string.format(" - 域名白名单(whitelist)使用分组:%s", LOCAL_GROUP or "默认"))
local fwd_group = LOCAL_GROUP
local ipset_flag = "#4:whitelist,#6:whitelist6"
local no_ipv6
if uci:get(appname, "@global_subscribe[0]", "subscribe_proxy") or "0" == "1" then
fwd_group = REMOTE_GROUP
ipset_flag = "#4:blacklist,#6:blacklist6"
if NO_PROXY_IPV6 == "1" then
ipset_flag = "#4:blacklist"
no_ipv6 = true
end
if REMOTE_FAKEDNS == "1" then
ipset_flag = nil
end
end
uci:foreach(appname, "subscribe_list", function(t)
local domain = get_domain_from_url(t.url)
if domain then
if no_ipv6 then
set_domain_address(domain, "#6")
end
set_domain_group(domain, fwd_group)
set_domain_ipset(domain, ipset_flag)
end
end)
log(string.format(" - 节点订阅域名(blacklist)使用分组:%s", fwd_group or "默认"))
--始终使用远程DNS解析代理黑名单列表
for line in io.lines("/usr/share/passwall/rules/proxy_host") do
if line ~= "" and not line:find("#") then
add_excluded_domain(line)
local ipset_flag = "#4:blacklist,#6:blacklist6"
if NO_PROXY_IPV6 == "1" then
set_domain_address(line, "#6")
ipset_flag = "#4:blacklist"
end
if REMOTE_FAKEDNS == "1" then
ipset_flag = nil
end
set_domain_group(line, REMOTE_GROUP)
set_domain_ipset(line, ipset_flag)
end
end
log(string.format(" - 代理域名表(blacklist)使用分组:%s", REMOTE_GROUP or "默认"))
--分流规则
if uci:get(appname, TCP_NODE, "protocol") == "_shunt" then
local t = uci:get_all(appname, TCP_NODE)
local default_node_id = t["default_node"] or "_direct"
uci:foreach(appname, "shunt_rules", function(s)
local _node_id = t[s[".name"]] or "nil"
if _node_id ~= "nil" and _node_id ~= "_blackhole" then
if _node_id == "_default" then
_node_id = default_node_id
end
fwd_group = nil
ipset_flag = nil
no_ipv6 = nil
if _node_id == "_direct" then
fwd_group = LOCAL_GROUP
ipset_flag = "#4:whitelist,#6:whitelist6"
else
fwd_group = REMOTE_GROUP
ipset_flag = "#4:shuntlist,#6:shuntlist6"
if NO_PROXY_IPV6 == "1" then
ipset_flag = "shuntlist"
no_ipv6 = true
end
if REMOTE_FAKEDNS == "1" then
ipset_flag = nil
end
end
local domain_list = s.domain_list or ""
for line in string.gmatch(domain_list, "[^\r\n]+") do
if line ~= "" and not line:find("#") and not line:find("regexp:") and not line:find("geosite:") and not line:find("ext:") then
if line:find("domain:") or line:find("full:") then
line = string.match(line, ":([^:]+)$")
end
add_excluded_domain(line)
if no_ipv6 then
set_domain_address(line, "#6")
end
set_domain_group(line, fwd_group)
set_domain_ipset(line, ipset_flag)
end
end
if _node_id ~= "_direct" then
log(string.format(" - V2ray/Xray分流规则(%s)使用分组:%s", s.remarks, fwd_group or "默认"))
end
end
end)
end
--如果没有使用回国模式
if not returnhome then
if fs.access("/usr/share/passwall/rules/gfwlist") then
local gfwlist_str = sys.exec('cat /usr/share/passwall/rules/gfwlist | grep -v -E "^#" | grep -v -E "' .. excluded_domain_str .. '"')
for line in string.gmatch(gfwlist_str, "[^\r\n]+") do
if line ~= "" then
local ipset_flag = "#4:gfwlist,#6:gfwlist6"
if NO_PROXY_IPV6 == "1" then
ipset_flag = "#4:gfwlist"
set_domain_address(line, "#6")
end
fwd_group = REMOTE_GROUP
if REMOTE_FAKEDNS == "1" then
ipset_flag = nil
end
set_domain_group(line, fwd_group)
set_domain_ipset(line, ipset_flag)
end
end
log(string.format(" - 防火墙域名表(gfwlist)使用分组:%s", fwd_group or "默认"))
end
if fs.access("/usr/share/passwall/rules/chnlist") and chnlist then
local chnlist_str = sys.exec('cat /usr/share/passwall/rules/chnlist | grep -v -E "^#" | grep -v -E "' .. excluded_domain_str .. '"')
for line in string.gmatch(chnlist_str, "[^\r\n]+") do
if line ~= "" then
set_domain_group(line, LOCAL_GROUP)
set_domain_ipset(line, "#4:chnroute,#6:chnroute6")
end
end
end
log(string.format(" - 中国域名表(chnroute)使用分组:%s", LOCAL_GROUP or "默认"))
else
if fs.access("/usr/share/passwall/rules/chnlist") then
local chnlist_str = sys.exec('cat /usr/share/passwall/rules/chnlist | grep -v -E "^#" | grep -v -E "' .. excluded_domain_str .. '"')
for line in string.gmatch(chnlist_str, "[^\r\n]+") do
if line ~= "" then
local ipset_flag = "#4:chnroute,#6:chnroute6"
if NO_PROXY_IPV6 == "1" then
ipset_flag = "#4:chnroute"
set_domain_address(line, "#6")
end
set_domain_group(line, REMOTE_GROUP)
if REMOTE_FAKEDNS == "1" then
ipset_flag = nil
end
set_domain_ipset(line, ipset_flag)
end
end
log(string.format(" - 中国域名表(chnroute)使用分组:%s", REMOTE_GROUP or "默认"))
end
end
local f_out = io.open(CACHE_DNS_FILE, "a")
for key, value in pairs(list1) do
local group_str = ""
local ipset_str = ""
local speed_check_mode_str = ""
local address_str = ""
if value.group and #value.group > 0 then
group_str = group_str .. value.group
end
if group_str ~= "" then
group_str = " -n " .. group_str
end
if value.ipsets and #value.ipsets > 0 then
for i, ipset in ipairs(value.ipsets) do
ipset_str = ipset_str .. ipset .. ","
end
ipset_str = ipset_str:sub(1, #ipset_str - 1)
end
if ipset_str ~= "" then
ipset_str = " -p " .. ipset_str
end
if value.address and #value.address > 0 then
address_str = address_str .. value.address
end
if address_str ~= "" then
address_str = " -a " .. address_str
end
if value.speed_check_mode and #value.speed_check_mode > 0 then
speed_check_mode_str = value.speed_check_mode
end
if speed_check_mode_str ~= "" then
speed_check_mode_str = " -c " .. speed_check_mode_str
end
local str = string.format("domain-rules /%s/ %s%s%s%s\n", key, group_str, ipset_str, address_str, speed_check_mode_str)
f_out:write(str)
end
f_out:close()
f_out = io.open(CACHE_MD5_FILE, "a")
f_out:write(md5)
f_out:close()
end
fs.symlink(CACHE_DNS_FILE, SMARTDNS_CONF)
sys.call(string.format('echo "conf-file %s" >> /etc/smartdns/custom.conf', SMARTDNS_CONF))
log(" - 请让SmartDNS作为Dnsmasq的上游或重定向")

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
#!/bin/sh
CONFIG=passwall
TMP_PATH=/tmp/etc/$CONFIG
TMP_BIN_PATH=$TMP_PATH/bin
TMP_SCRIPT_FUNC_PATH=$TMP_PATH/script_func
TMP_ID_PATH=$TMP_PATH/id
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
for filename in $(ls ${TMP_SCRIPT_FUNC_PATH}); 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/passwall.log
eval $(echo "nohup ${cmd} 2>&1 &") >/dev/null 2>&1 &
fi
done
rm -f $LOCK_FILE
sleep 58s
done

View File

@ -0,0 +1,401 @@
#!/usr/bin/lua
require 'nixio'
require 'luci.sys'
local luci = luci
local ucic = luci.model.uci.cursor()
local jsonc = require "luci.jsonc"
local name = 'passwall'
local api = require ("luci.model.cbi." .. name .. ".api.api")
local arg1 = arg[1]
local rule_path = "/usr/share/" .. name .. "/rules"
local reboot = 0
local gfwlist_update = 0
local chnroute_update = 0
local chnroute6_update = 0
local chnlist_update = 0
local geoip_update = 0
local geosite_update = 0
-- match comments/title/whitelist/ip address/excluded_domain
local comment_pattern = "^[!\\[@]+"
local ip_pattern = "^%d+%.%d+%.%d+%.%d+"
local ip4_ipset_pattern = "^%d+%.%d+%.%d+%.%d+[%/][%d]+$"
local ip6_ipset_pattern = ":-[%x]+%:+[%x]-[%/][%d]+$"
local domain_pattern = "([%w%-%_]+%.[%w%.%-%_]+)[%/%*]*"
local excluded_domain = {"apple.com","sina.cn","sina.com.cn","baidu.com","byr.cn","jlike.com","weibo.com","zhongsou.com","youdao.com","sogou.com","so.com","soso.com","aliyun.com","taobao.com","jd.com","qq.com","bing.com"}
local gfwlist_url = ucic:get(name, "@global_rules[0]", "gfwlist_url") or {"https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/gfw.txt"}
local chnroute_url = ucic:get(name, "@global_rules[0]", "chnroute_url") or {"https://ispip.clang.cn/all_cn.txt"}
local chnroute6_url = ucic:get(name, "@global_rules[0]", "chnroute6_url") or {"https://ispip.clang.cn/all_cn_ipv6.txt"}
local chnlist_url = ucic:get(name, "@global_rules[0]", "chnlist_url") or {"https://cdn.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/accelerated-domains.china.conf","https://cdn.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/apple.china.conf","https://cdn.jsdelivr.net/gh/felixonmars/dnsmasq-china-list/google.china.conf"}
local geoip_api = "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest"
local geosite_api = "https://api.github.com/repos/Loyalsoldier/v2ray-rules-dat/releases/latest"
local v2ray_asset_location = ucic:get_first(name, 'global_rules', "v2ray_location_asset", "/usr/share/v2ray/")
local log = function(...)
if arg1 then
local result = os.date("%Y-%m-%d %H:%M:%S: ") .. table.concat({...}, " ")
if arg1 == "log" then
local f, err = io.open("/tmp/log/passwall.log", "a")
if f and err == nil then
f:write(result .. "\n")
f:close()
end
elseif arg1 == "print" then
print(result)
end
end
end
-- trim
local function trim(text)
if not text or text == "" then return "" end
return (string.gsub(text, "^%s*(.-)%s*$", "%1"))
end
-- curl
local function curl(url, file)
local cmd = "curl -skL -w %{http_code} --retry 3 --connect-timeout 3 '" .. url .. "'"
if file then
cmd = cmd .. " -o " .. file
end
local stdout = luci.sys.exec(cmd)
if file then
return tonumber(trim(stdout))
else
return trim(stdout)
end
end
--check excluded domain
local function check_excluded_domain(value)
for k,v in ipairs(excluded_domain) do
if value:find(v) then
return true
end
end
end
local function line_count(file_path)
local num = 0
for _ in io.lines(file_path) do
num = num + 1
end
return num;
end
--fetch rule
local function fetch_rule(rule_name,rule_type,url,exclude_domain)
local sret = 200
local sret_tmp = 0
local domains = {}
local file_tmp = "/tmp/" ..rule_name.. "_tmp"
local download_file_tmp = "/tmp/" ..rule_name.. "_dl"
local unsort_file_tmp = "/tmp/" ..rule_name.. "_unsort"
log(rule_name.. " 开始更新...")
for k,v in ipairs(url) do
sret_tmp = curl(v, download_file_tmp..k)
if sret_tmp == 200 then
if rule_name == "gfwlist" then
local domains = {}
local gfwlist = io.open(download_file_tmp..k, "r")
local decode = api.base64Decode(gfwlist:read("*all"))
gfwlist:close()
gfwlist = io.open(download_file_tmp..k, "w")
gfwlist:write(decode)
gfwlist:close()
end
if rule_type == "domain" and exclude_domain == true then
for line in io.lines(download_file_tmp..k) do
if not (string.find(line, comment_pattern) or string.find(line, ip_pattern) or check_excluded_domain(line)) then
local start, finish, match = string.find(line, domain_pattern)
if (start) then
domains[match] = true
end
end
end
elseif rule_type == "domain" then
for line in io.lines(download_file_tmp..k) do
if not (string.find(line, comment_pattern) or string.find(line, ip_pattern)) then
local start, finish, match = string.find(line, domain_pattern)
if (start) then
domains[match] = true
end
end
end
elseif rule_type == "ip4" then
local out = io.open(unsort_file_tmp, "a")
for line in io.lines(download_file_tmp..k) do
local start, finish, match = string.find(line, ip4_ipset_pattern)
if (start) then
out:write(string.format("%s\n", line))
end
end
out:close()
elseif rule_type == "ip6" then
local out = io.open(unsort_file_tmp, "a")
for line in io.lines(download_file_tmp..k) do
local start, finish, match = string.find(line, ip6_ipset_pattern)
if (start) then
out:write(string.format("%s\n", line))
end
end
out:close()
end
os.remove(download_file_tmp..k)
else
sret = 0
log(rule_name.. "" ..k.. "条规则:" ..v.. "下载失败!")
end
end
if sret == 200 then
if rule_type == "domain" then
local out = io.open(unsort_file_tmp, "w")
for k,v in pairs(domains) do
out:write(string.format("%s\n", k))
end
out:close()
end
luci.sys.call("cat " ..unsort_file_tmp.. " | sort -u > "..file_tmp)
os.remove(unsort_file_tmp)
end
if sret == 200 then
local old_md5 = luci.sys.exec("echo -n $(md5sum " .. rule_path .. "/" ..rule_name.. " | awk '{print $1}')")
local new_md5 = luci.sys.exec("echo -n $([ -f '" ..file_tmp.. "' ] && md5sum " ..file_tmp.." | awk '{print $1}')")
if old_md5 ~= new_md5 then
local count = line_count(file_tmp)
luci.sys.exec("mv -f "..file_tmp .. " " ..rule_path .. "/" ..rule_name)
reboot = 1
log(rule_name.. " 更新成功,总规则数 " ..count.. " 条。")
else
log(rule_name.. " 版本一致,无需更新。")
end
else
log(rule_name.. " 文件下载失败!")
end
os.remove(file_tmp)
return 0
end
local function fetch_gfwlist()
fetch_rule("gfwlist","domain",gfwlist_url,true)
end
local function fetch_chnroute()
fetch_rule("chnroute","ip4",chnroute_url,false)
end
local function fetch_chnroute6()
fetch_rule("chnroute6","ip6",chnroute6_url,false)
end
local function fetch_chnlist()
fetch_rule("chnlist","domain",chnlist_url,false)
end
--获取geoip
local function fetch_geoip()
--请求geoip
xpcall(function()
local json_str = curl(geoip_api)
local json = jsonc.parse(json_str)
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 json_str = curl(geosite_api)
local json = jsonc.parse(json_str)
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
if arg[2]:find("gfwlist") then
gfwlist_update = 1
end
if arg[2]:find("chnroute") then
chnroute_update = 1
end
if arg[2]:find("chnroute6") then
chnroute6_update = 1
end
if arg[2]:find("chnlist") then
chnlist_update = 1
end
if arg[2]:find("geoip") then
geoip_update = 1
end
if arg[2]:find("geosite") then
geosite_update = 1
end
else
gfwlist_update = ucic:get_first(name, 'global_rules', "gfwlist_update", 1)
chnroute_update = ucic:get_first(name, 'global_rules', "chnroute_update", 1)
chnroute6_update = ucic:get_first(name, 'global_rules', "chnroute6_update", 1)
chnlist_update = ucic:get_first(name, 'global_rules', "chnlist_update", 1)
geoip_update = ucic:get_first(name, 'global_rules', "geoip_update", 1)
geosite_update = ucic:get_first(name, 'global_rules', "geosite_update", 1)
end
if gfwlist_update == 0 and chnroute_update == 0 and chnroute6_update == 0 and chnlist_update == 0 and geoip_update == 0 and geosite_update == 0 then
os.exit(0)
end
log("开始更新规则...")
if tonumber(gfwlist_update) == 1 then
xpcall(fetch_gfwlist,function(e)
log(e)
log(debug.traceback())
log('更新gfwlist发生错误...')
end)
end
if tonumber(chnroute_update) == 1 then
xpcall(fetch_chnroute,function(e)
log(e)
log(debug.traceback())
log('更新chnroute发生错误...')
end)
end
if tonumber(chnroute6_update) == 1 then
xpcall(fetch_chnroute6,function(e)
log(e)
log(debug.traceback())
log('更新chnroute6发生错误...')
end)
end
if tonumber(chnlist_update) == 1 then
xpcall(fetch_chnlist,function(e)
log(e)
log(debug.traceback())
log('更新chnlist发生错误...')
end)
end
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'), "gfwlist_update", gfwlist_update)
ucic:set(name, ucic:get_first(name, 'global_rules'), "chnroute_update", chnroute_update)
ucic:set(name, ucic:get_first(name, 'global_rules'), "chnroute6_update", chnroute6_update)
ucic:set(name, ucic:get_first(name, 'global_rules'), "chnlist_update", chnlist_update)
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("重启服务,应用新的规则。")
luci.sys.call("/usr/share/" .. name .. "/iptables.sh flush_ipset > /dev/null 2>&1 &")
end
log("规则更新完毕...")

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
apple.com
microsoft.com
dyndns.com
douyucdn.cn
douyucdn2.cn
#steam
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
# Synology ddns
account.synology.com
checkip.dyndns.org
checkip.synology.com
checkipv6.dyndns.org
checkipv6.synology.com
checkport.synology.com
ddns.synology.com

View File

@ -0,0 +1,6 @@
114.114.114.114
114.114.115.115
223.5.5.5
223.6.6.6
119.29.29.29
180.76.76.76

View 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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
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

View File

@ -0,0 +1,13 @@
::/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

View File

@ -0,0 +1,13 @@
sspanel.net
v2ex.com
#google
googleapis.cn
googleapis.com
google.com.tw
google.com.hk
gstatic.com
xn--ngstr-lra8j.com
#github
github.com

View File

@ -0,0 +1,19 @@
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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,274 @@
#!/bin/sh
CONFIG=passwall
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" ] && {
if [ "${_type}" == "socks" ]; then
local _address=$(config_n_get ${node_id} address)
local _port=$(config_n_get ${node_id} port)
[ -n "${_address}" ] && [ -n "${_port}" ] && {
local curlx="socks5h://${_address}:${_port}"
local _username=$(config_n_get ${node_id} username)
local _password=$(config_n_get ${node_id} password)
[ -n "${_username}" ] && [ -n "${_password}" ] && curlx="socks5h://${_username}:${_password}@${_address}:${_port}"
}
else
local _tmp_port=$(/usr/share/${CONFIG}/app.sh get_new_port 61080 tcp)
/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}"
fi
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" ] && {
if [ "${_type}" == "socks" ]; then
local _address=$(config_n_get ${node_id} address)
local _port=$(config_n_get ${node_id} port)
[ -n "${_address}" ] && [ -n "${_port}" ] && {
local curlx="socks5h://${_address}:${_port}"
local _username=$(config_n_get ${node_id} username)
local _password=$(config_n_get ${node_id} password)
[ -n "${_username}" ] && [ -n "${_password}" ] && curlx="socks5h://${_username}:${_password}@${_address}:${_port}"
}
else
local _tmp_port=$(/usr/share/${CONFIG}/app.sh get_new_port 61080 tcp)
/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}"
fi
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 tcp_node nil)
test_auto_switch() {
flag=$(expr $flag + 1)
local TYPE=$1
local b_tcp_nodes=$2
local now_node=$3
[ -z "$now_node" ] && {
if [ -f "/tmp/etc/$CONFIG/id/${TYPE}" ]; then
now_node=$(cat /tmp/etc/$CONFIG/id/${TYPE})
if [ "$(config_n_get $now_node protocol nil)" = "_shunt" ]; then
if [ "$shunt_logic" == "1" ] && [ -f "/tmp/etc/$CONFIG/id/${TYPE}_default" ]; then
now_node=$(cat /tmp/etc/$CONFIG/id/${TYPE}_default)
elif [ "$shunt_logic" == "2" ] && [ -f "/tmp/etc/$CONFIG/id/${TYPE}_main" ]; then
now_node=$(cat /tmp/etc/$CONFIG/id/${TYPE}_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=${TYPE} new_node=${main_node} shunt_logic=${shunt_logic}
[ $? -eq 0 ] && {
echolog "自动切换检测:${TYPE}节点切换完毕!"
[ "$shunt_logic" != "0" ] && {
local tcp_node=$(config_t_get global tcp_node nil)
[ "$(config_n_get $tcp_node protocol nil)" = "_shunt" ] && {
if [ "$shunt_logic" == "1" ]; then
uci set $CONFIG.$tcp_node.default_node="$main_node"
elif [ "$shunt_logic" == "2" ]; then
uci set $CONFIG.$tcp_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_tcp_nodes | grep $now_node)
# 判断当前节点是否存在于备用节点列表里
if [ -z "$in_backup_nodes" ]; then
# 如果不存在,设置第一个节点为新的节点
new_node=$(echo $b_tcp_nodes | awk -F ' ' '{print $1}')
else
# 如果存在,设置下一个备用节点为新的节点
#local count=$(expr $(echo $b_tcp_nodes | grep -o ' ' | wc -l) + 1)
local next_node=$(echo $b_tcp_nodes | awk -F "$now_node" '{print $2}' | awk -F " " '{print $1}')
if [ -z "$next_node" ]; then
new_node=$(echo $b_tcp_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].tcp_node=$new_node
[ -z "$(echo $b_tcp_nodes | grep $main_node)" ] && uci add_list $CONFIG.@auto_switch[0].tcp_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=${TYPE} new_node=${new_node} shunt_logic=${shunt_logic}
[ $? -eq 0 ] && {
[ "$restore_switch" == "1" ] && [ "$shunt_logic" != "0" ] && {
local tcp_node=$(config_t_get global tcp_node nil)
[ "$(config_n_get $tcp_node protocol nil)" = "_shunt" ] && {
if [ "$shunt_logic" == "1" ]; then
uci set $CONFIG.$tcp_node.default_node="$main_node"
elif [ "$shunt_logic" == "2" ]; then
uci set $CONFIG.$tcp_node.main_node="$main_node"
fi
uci commit $CONFIG
}
}
echolog "自动切换检测:${TYPE}节点切换完毕!"
}
return 0
else
test_auto_switch ${TYPE} "${b_tcp_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
TCP_NODE=$(config_t_get auto_switch tcp_node nil)
[ -n "$TCP_NODE" -a "$TCP_NODE" != "nil" ] && {
TCP_NODE=$(echo $TCP_NODE | tr -s ' ' '\n' | uniq | tr -s '\n' ' ')
test_auto_switch TCP "$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

View File

@ -0,0 +1,11 @@
{
"luci-app-passwall": {
"description": "Grant UCI access for luci-app-passwall",
"read": {
"uci": [ "passwall", "passwall_server" ]
},
"write": {
"uci": [ "passwall", "passwall_server" ]
}
}
}

119
luci-app-ssr-plus/Makefile Normal file
View File

@ -0,0 +1,119 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-ssr-plus
PKG_VERSION:=185
PKG_RELEASE:=2
PKG_CONFIG_DEPENDS:= \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Kcptun \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_IPT2Socks \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Redsocks2 \
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_Trojan \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Plugin \
CONFIG_PACKAGE_$(PKG_NAME)_INCLUDE_Xray
LUCI_TITLE:=SS/SSR/V2Ray/Trojan/NaiveProxy/Socks5/Tun LuCI interface
LUCI_PKGARCH:=all
LUCI_DEPENDS:= \
@(PACKAGE_libustream-mbedtls||PACKAGE_libustream-openssl||PACKAGE_libustream-wolfssl) \
+coreutils +coreutils-base64 +dns2socks +dnsmasq-full +ipset +kmod-ipt-nat \
+ip-full +iptables-mod-tproxy +lua +libuci-lua +microsocks +pdnsd-alt \
+tcping +resolveip +shadowsocksr-libev-ssr-check +uclient-fetch \
+PACKAGE_$(PKG_NAME)_INCLUDE_Kcptun:kcptun-client \
+PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy:naiveproxy \
+PACKAGE_$(PKG_NAME)_INCLUDE_IPT2Socks:ipt2socks \
+PACKAGE_$(PKG_NAME)_INCLUDE_Redsocks2:redsocks2 \
+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_Trojan:trojan \
+PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Plugin:v2ray-plugin \
+PACKAGE_$(PKG_NAME)_INCLUDE_Xray:curl \
+PACKAGE_$(PKG_NAME)_INCLUDE_Xray:xray-core
define Package/$(PKG_NAME)/config
config PACKAGE_$(PKG_NAME)_INCLUDE_Kcptun
bool "Include Kcptun"
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_NaiveProxy
bool "Include NaiveProxy"
depends on !(arc||armeb||mips||mips64||powerpc||TARGET_gemini)
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_IPT2Socks
bool "Include ipt2socks"
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_Redsocks2
bool "Include Redsocks2"
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Client
bool "Include Shadowsocks Libev Client"
default y if i386||x86_64||arm
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Libev_Server
bool "Include Shadowsocks Libev Server"
default y if i386||x86_64||arm
config PACKAGE_$(PKG_NAME)_INCLUDE_Shadowsocks_Rust_Client
bool "Include Shadowsocks Rust Client"
depends on aarch64||arm||i386||mips||mipsel||x86_64
depends on !(TARGET_x86_geode||TARGET_x86_legacy)
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
depends on !(TARGET_x86_geode||TARGET_x86_legacy)
default y if aarch64
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 y if i386||x86_64||arm
config PACKAGE_$(PKG_NAME)_INCLUDE_Simple_Obfs
bool "Include Shadowsocks Simple Obfs Plugin"
default y if i386||x86_64||arm
config PACKAGE_$(PKG_NAME)_INCLUDE_Trojan
bool "Include Trojan"
select PACKAGE_$(PKG_NAME)_INCLUDE_IPT2Socks
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_V2ray_Plugin
bool "Include Shadowsocks V2ray Plugin"
default n
config PACKAGE_$(PKG_NAME)_INCLUDE_Xray
bool "Include Xray"
default y if aarch64||arm||i386||x86_64
endef
define Package/$(PKG_NAME)/conffiles
/etc/config/shadowsocksr
/etc/ssrplus/
endef
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature

View File

@ -0,0 +1,143 @@
-- Copyright (C) 2017 yushi studio <ywb94@qq.com>
-- Licensed to the public under the GNU General Public License v3.
module("luci.controller.shadowsocksr", package.seeall)
function index()
if not nixio.fs.access("/etc/config/shadowsocksr") then
call("act_reset")
end
local page
page = entry({"admin", "services", "shadowsocksr"}, alias("admin", "services", "shadowsocksr", "client"), _("ShadowSocksR Plus+"), 10)
page.dependent = true
page.acl_depends = { "luci-app-ssr-plus" }
entry({"admin", "services", "shadowsocksr", "client"}, cbi("shadowsocksr/client"), _("SSR Client"), 10).leaf = true
entry({"admin", "services", "shadowsocksr", "servers"}, arcombine(cbi("shadowsocksr/servers", {autoapply = true}), cbi("shadowsocksr/client-config")), _("Severs Nodes"), 20).leaf = true
entry({"admin", "services", "shadowsocksr", "control"}, cbi("shadowsocksr/control"), _("Access Control"), 30).leaf = true
entry({"admin", "services", "shadowsocksr", "advanced"}, cbi("shadowsocksr/advanced"), _("Advanced Settings"), 50).leaf = true
entry({"admin", "services", "shadowsocksr", "server"}, arcombine(cbi("shadowsocksr/server"), cbi("shadowsocksr/server-config")), _("SSR Server"), 60).leaf = true
entry({"admin", "services", "shadowsocksr", "status"}, form("shadowsocksr/status"), _("Status"), 70).leaf = true
entry({"admin", "services", "shadowsocksr", "check"}, call("check_status"))
entry({"admin", "services", "shadowsocksr", "refresh"}, call("refresh_data"))
entry({"admin", "services", "shadowsocksr", "subscribe"}, call("subscribe"))
entry({"admin", "services", "shadowsocksr", "checkport"}, call("check_port"))
entry({"admin", "services", "shadowsocksr", "log"}, form("shadowsocksr/log"), _("Log"), 80).leaf = true
entry({"admin", "services", "shadowsocksr", "run"}, call("act_status"))
entry({"admin", "services", "shadowsocksr", "ping"}, call("act_ping"))
entry({"admin", "services", "shadowsocksr", "reset"}, call("act_reset"))
entry({"admin", "services", "shadowsocksr", "restart"}, call("act_restart"))
entry({"admin", "services", "shadowsocksr", "delete"}, call("act_delete"))
entry({"admin", "services", "shadowsocksr", "cache"}, call("act_cache"))
end
function subscribe()
luci.sys.call("/usr/bin/lua /usr/share/shadowsocksr/subscribe.lua >>/var/log/ssrplus.log")
luci.http.prepare_content("application/json")
luci.http.write_json({ret = 1})
end
function act_status()
local e = {}
e.running = luci.sys.call("busybox ps -w | grep ssr-retcp | grep -v grep >/dev/null") == 0
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function act_ping()
local e = {}
local domain = luci.http.formvalue("domain")
local port = luci.http.formvalue("port")
local transport = luci.http.formvalue("transport")
local wsPath = luci.http.formvalue("wsPath")
local tls = luci.http.formvalue("tls")
e.index = luci.http.formvalue("index")
local iret = luci.sys.call("ipset add ss_spec_wan_ac " .. domain .. " 2>/dev/null")
if transport == "ws" then
local prefix = tls=='1' and "https://" or "http://"
local address = prefix..domain..':'..port..wsPath
local result = luci.sys.exec("curl --http1.1 -m 2 -ksN -o /dev/null -w 'time_connect=%{time_connect}\nhttp_code=%{http_code}' -H 'Connection: Upgrade' -H 'Upgrade: websocket' -H 'Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==' -H 'Sec-WebSocket-Version: 13' "..address)
e.socket = string.match(result,"http_code=(%d+)")=="101"
e.ping = tonumber(string.match(result, "time_connect=(%d+.%d%d%d)"))*1000
else
local socket = nixio.socket("inet", "stream")
socket:setopt("socket", "rcvtimeo", 3)
socket:setopt("socket", "sndtimeo", 3)
e.socket = socket:connect(domain, port)
socket:close()
-- e.ping = luci.sys.exec("ping -c 1 -W 1 %q 2>&1 | grep -o 'time=[0-9]*.[0-9]' | awk -F '=' '{print$2}'" % domain)
-- if (e.ping == "") then
e.ping = luci.sys.exec(string.format("echo -n $(tcping -q -c 1 -i 1 -t 2 -p %s %s 2>&1 | grep -o 'time=[0-9]*' | awk -F '=' '{print $2}') 2>/dev/null", port, domain))
-- end
end
if (iret == 0) then
luci.sys.call(" ipset del ss_spec_wan_ac " .. domain)
end
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function check_status()
local e = {}
e.ret = luci.sys.call("/usr/bin/ssr-check www." .. luci.http.formvalue("set") .. ".com 80 3 1")
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end
function refresh_data()
local set = luci.http.formvalue("set")
local retstring = loadstring("return " .. luci.sys.exec("/usr/bin/lua /usr/share/shadowsocksr/update.lua " .. set))()
luci.http.prepare_content("application/json")
luci.http.write_json(retstring)
end
function check_port()
local retstring = "<br /><br />"
local s
local server_name = ""
local uci = luci.model.uci.cursor()
local iret = 1
uci:foreach("shadowsocksr", "servers", function(s)
if s.alias then
server_name = s.alias
elseif s.server and s.server_port then
server_name = "%s:%s" % {s.server, s.server_port}
end
iret = luci.sys.call("ipset add ss_spec_wan_ac " .. s.server .. " 2>/dev/null")
socket = nixio.socket("inet", "stream")
socket:setopt("socket", "rcvtimeo", 3)
socket:setopt("socket", "sndtimeo", 3)
ret = socket:connect(s.server, s.server_port)
if tostring(ret) == "true" then
socket:close()
retstring = retstring .. "<font color = 'green'>[" .. server_name .. "] OK.</font><br />"
else
retstring = retstring .. "<font color = 'red'>[" .. server_name .. "] Error.</font><br />"
end
if iret == 0 then
luci.sys.call("ipset del ss_spec_wan_ac " .. s.server)
end
end)
luci.http.prepare_content("application/json")
luci.http.write_json({ret = retstring})
end
function act_reset()
luci.sys.call("/etc/init.d/shadowsocksr reset &")
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr"))
end
function act_restart()
luci.sys.call("/etc/init.d/shadowsocksr restart &")
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr"))
end
function act_delete()
luci.sys.call("/etc/init.d/shadowsocksr restart &")
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr", "servers"))
end
function act_cache()
local e = {}
e.ret = luci.sys.call("pdnsd-ctl -c /var/etc/ssrplus/pdnsd empty-cache >/dev/null")
luci.http.prepare_content("application/json")
luci.http.write_json(e)
end

View File

@ -0,0 +1,102 @@
local uci = luci.model.uci.cursor()
local server_table = {}
uci:foreach("shadowsocksr", "servers", function(s)
if s.alias then
server_table[s[".name"]] = "[%s]:%s" % {string.upper(s.v2ray_protocol or s.type), s.alias}
elseif s.server and s.server_port then
server_table[s[".name"]] = "[%s]:%s:%s" % {string.upper(s.v2ray_protocol or s.type), s.server, s.server_port}
end
end)
local key_table = {}
for key, _ in pairs(server_table) do
table.insert(key_table, key)
end
table.sort(key_table)
m = Map("shadowsocksr")
-- [[ global ]]--
s = m:section(TypedSection, "global", translate("Server failsafe auto swith and custom update settings"))
s.anonymous = true
-- o = s:option(Flag, "monitor_enable", translate("Enable Process Deamon"))
-- o.rmempty = false
-- o.default = "1"
o = s:option(Flag, "enable_switch", translate("Enable Auto Switch"))
o.rmempty = false
o.default = "1"
o = s:option(Value, "switch_time", translate("Switch check cycly(second)"))
o.datatype = "uinteger"
o:depends("enable_switch", "1")
o.default = 667
o = s:option(Value, "switch_timeout", translate("Check timout(second)"))
o.datatype = "uinteger"
o:depends("enable_switch", "1")
o.default = 5
o = s:option(Value, "switch_try_count", translate("Check Try Count"))
o.datatype = "uinteger"
o:depends("enable_switch", "1")
o.default = 3
o = s:option(Value, "gfwlist_url", translate("gfwlist Update url"))
o:value("https://cdn.jsdelivr.net/gh/YW5vbnltb3Vz/domain-list-community@release/gfwlist.txt", translate("v2fly/domain-list-community"))
o:value("https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/gfw.txt", translate("Loyalsoldier/v2ray-rules-dat"))
o:value("https://cdn.jsdelivr.net/gh/Loukky/gfwlist-by-loukky/gfwlist.txt", translate("Loukky/gfwlist-by-loukky"))
o:value("https://cdn.jsdelivr.net/gh/gfwlist/gfwlist/gfwlist.txt", translate("gfwlist/gfwlist"))
o.default = "https://cdn.jsdelivr.net/gh/YW5vbnltb3Vz/domain-list-community@release/gfwlist.txt"
o = s:option(Value, "chnroute_url", translate("Chnroute Update url"))
o:value("https://ispip.clang.cn/all_cn.txt", translate("Clang.CN"))
o:value("https://ispip.clang.cn/all_cn_cidr.txt", translate("Clang.CN.CIDR"))
o.default = "https://ispip.clang.cn/all_cn.txt"
o = s:option(Flag, "netflix_enable", translate("Enable Netflix Mode"))
o.rmempty = false
o = s:option(Value, "nfip_url", translate("nfip_url"))
o:value("https://cdn.jsdelivr.net/gh/QiuSimons/Netflix_IP/NF_only.txt", translate("Netflix IP Only"))
o:value("https://cdn.jsdelivr.net/gh/QiuSimons/Netflix_IP/getflix.txt", translate("Netflix and AWS"))
o.default = "https://cdn.jsdelivr.net/gh/QiuSimons/Netflix_IP/NF_only.txt"
o.description = translate("Customize Netflix IP Url")
o:depends("netflix_enable", "1")
o = s:option(Flag, "adblock", translate("Enable adblock"))
o.rmempty = false
o = s:option(Value, "adblock_url", translate("adblock_url"))
o:value("https://raw.githubusercontent.com/neodevpro/neodevhost/master/lite_dnsmasq.conf", translate("NEO DEV HOST Lite"))
o:value("https://raw.githubusercontent.com/neodevpro/neodevhost/master/dnsmasq.conf", translate("NEO DEV HOST Full"))
o:value("https://anti-ad.net/anti-ad-for-dnsmasq.conf", translate("anti-AD"))
o.default = "https://raw.githubusercontent.com/neodevpro/neodevhost/master/lite_dnsmasq.conf"
o:depends("adblock", "1")
o.description = translate("Support AdGuardHome and DNSMASQ format list")
o = s:option(Button, "reset", translate("Reset to defaults"))
o.rawhtml = true
o.template = "shadowsocksr/reset"
-- [[ SOCKS5 Proxy ]]--
s = m:section(TypedSection, "socks5_proxy", translate("Global SOCKS5 Proxy Server"))
s.anonymous = true
o = s:option(ListValue, "server", translate("Server"))
o:value("nil", translate("Disable"))
o:value("same", translate("Same as Global Server"))
for _, key in pairs(key_table) do
o:value(key, server_table[key])
end
o.default = "nil"
o.rmempty = false
o = s:option(Value, "local_port", translate("Local Port"))
o.datatype = "port"
o.default = 1080
o.rmempty = false
return m

View File

@ -0,0 +1,655 @@
-- Copyright (C) 2017 yushi studio <ywb94@qq.com> github.com/ywb94
-- Licensed to the public under the GNU General Public License v3.
require "nixio.fs"
require "luci.sys"
require "luci.http"
local m, s, o, kcp_enable
local sid = arg[1]
local uuid = luci.sys.exec("cat /proc/sys/kernel/random/uuid")
function is_finded(e)
return luci.sys.exec('type -t -p "%s"' % e) ~= "" and true or false
end
local server_table = {}
local encrypt_methods = {
-- ssr
"none",
"table",
"rc4",
"rc4-md5-6",
"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",
"cast5-cfb",
"des-cfb",
"idea-cfb",
"rc2-cfb",
"seed-cfb",
"salsa20",
"chacha20",
"chacha20-ietf"
}
local encrypt_methods_ss = {
-- aead
"aes-128-gcm",
"aes-192-gcm",
"aes-256-gcm",
"chacha20-ietf-poly1305",
"xchacha20-ietf-poly1305"
--[[ stream
"none",
"plain",
"table",
"rc4",
"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" ]]
}
local encrypt_methods_v2ray_ss = {
-- xray_ss
"none",
"plain",
-- aead
"aes-128-gcm",
"aes-256-gcm",
"chacha20-poly1305",
"chacha20-ietf-poly1305",
"xchacha20-ietf-poly1305",
"aead_aes_128_gcm",
"aead_aes_256_gcm",
"aead_chacha20_poly1305",
"aead_xchacha20_poly1305"
}
local protocol = {
-- ssr
"origin",
"verify_deflate",
"auth_sha1_v4",
"auth_aes128_sha1",
"auth_aes128_md5",
"auth_chain_a",
"auth_chain_b",
"auth_chain_c",
"auth_chain_d",
"auth_chain_e",
"auth_chain_f"
}
obfs = {
-- ssr
"plain",
"http_simple",
"http_post",
"random_head",
"tls1.2_ticket_auth"
}
local securitys = {
-- vmess
"auto",
"none",
"zero",
"aes-128-gcm",
"chacha20-poly1305"
}
local flows = {
-- xlts
"xtls-rprx-origin",
"xtls-rprx-origin-udp443",
"xtls-rprx-direct",
"xtls-rprx-direct-udp443",
"xtls-rprx-splice",
"xtls-rprx-splice-udp443"
}
m = Map("shadowsocksr", translate("Edit ShadowSocksR Server"))
m.redirect = luci.dispatcher.build_url("admin/services/shadowsocksr/servers")
if m.uci:get("shadowsocksr", sid) ~= "servers" then
luci.http.redirect(m.redirect)
return
end
-- [[ Servers Setting ]]--
s = m:section(NamedSection, sid, "servers")
s.anonymous = true
s.addremove = false
o = s:option(DummyValue, "ssr_url", "SS/SSR/V2RAY/TROJAN URL")
o.rawhtml = true
o.template = "shadowsocksr/ssrurl"
o.value = sid
o = s:option(ListValue, "type", translate("Server Node Type"))
if is_finded("xray") or is_finded("v2ray") then
o:value("v2ray", translate("V2Ray/XRay"))
end
if is_finded("ssr-redir") then
o:value("ssr", translate("ShadowsocksR"))
end
if is_finded("sslocal") or is_finded("ss-redir") then
o:value("ss", translate("Shadowsocks New Version"))
end
if is_finded("trojan") then
o:value("trojan", translate("Trojan"))
end
if is_finded("naive") then
o:value("naiveproxy", translate("NaiveProxy"))
end
if is_finded("ipt2socks") then
o:value("socks5", translate("Socks5"))
end
if is_finded("redsocks2") then
o:value("tun", translate("Network Tunnel"))
end
o.description = translate("Using incorrect encryption mothod may causes service fail to start")
o = s:option(Value, "alias", translate("Alias(optional)"))
o = s:option(ListValue, "iface", translate("Network interface to use"))
for _, e in ipairs(luci.sys.net.devices()) do
if e ~= "lo" then
o:value(e)
end
end
o:depends("type", "tun")
o.description = translate("Redirect traffic to this network interface")
o = s:option(ListValue, "v2ray_protocol", translate("V2Ray/XRay protocol"))
o:value("vless", translate("VLESS"))
o:value("vmess", translate("VMess"))
o:value("trojan", translate("Trojan"))
o:value("shadowsocks", translate("Shadowsocks"))
o:value("socks", translate("Socks"))
o:value("http", translate("HTTP"))
o:depends("type", "v2ray")
o = s:option(Value, "server", translate("Server Address"))
o.datatype = "host"
o.rmempty = false
o:depends("type", "ssr")
o:depends("type", "ss")
o:depends("type", "v2ray")
o:depends("type", "trojan")
o:depends("type", "naiveproxy")
o:depends("type", "socks5")
o = s:option(Value, "server_port", translate("Server Port"))
o.datatype = "port"
o.rmempty = false
o:depends("type", "ssr")
o:depends("type", "ss")
o:depends("type", "v2ray")
o:depends("type", "trojan")
o:depends("type", "naiveproxy")
o:depends("type", "socks5")
o = s:option(Flag, "auth_enable", translate("Enable Authentication"))
o.rmempty = false
o.default = "0"
o:depends("type", "socks5")
o:depends({type = "v2ray", v2ray_protocol = "http"})
o:depends({type = "v2ray", v2ray_protocol = "socks"})
o = s:option(Value, "username", translate("Username"))
o.rmempty = true
o:depends("type", "naiveproxy")
o:depends({type = "socks5", auth_enable = true})
o:depends({type = "v2ray", v2ray_protocol = "http", auth_enable = true})
o:depends({type = "v2ray", v2ray_protocol = "socks", auth_enable = true})
o = s:option(Value, "password", translate("Password"))
o.password = true
o.rmempty = true
o:depends("type", "ssr")
o:depends("type", "ss")
o:depends("type", "trojan")
o:depends("type", "naiveproxy")
o:depends({type = "socks5", auth_enable = true})
o:depends({type = "v2ray", v2ray_protocol = "http", auth_enable = true})
o:depends({type = "v2ray", v2ray_protocol = "socks", auth_enable = true})
o:depends({type = "v2ray", v2ray_protocol = "shadowsocks"})
o:depends({type = "v2ray", v2ray_protocol = "trojan"})
o = s:option(ListValue, "encrypt_method", translate("Encrypt Method"))
for _, v in ipairs(encrypt_methods) do
o:value(v)
end
o.rmempty = true
o:depends("type", "ssr")
o = s:option(ListValue, "encrypt_method_ss", translate("Encrypt Method"))
for _, v in ipairs(encrypt_methods_ss) do
o:value(v)
end
o.rmempty = true
o:depends("type", "ss")
o = s:option(ListValue, "encrypt_method_v2ray_ss", translate("Encrypt Method"))
for _, v in ipairs(encrypt_methods_v2ray_ss) do
o:value(v)
end
o.rmempty = true
o:depends({type = "v2ray", v2ray_protocol = "shadowsocks"})
o = s:option(Flag, "ivCheck", translate("Bloom Filter"))
o.rmempty = true
o:depends({type = "v2ray", v2ray_protocol = "shadowsocks"})
o.default = "1"
-- Shadowsocks Plugin
o = s:option(Value, "plugin", translate("Obfs"))
o:value("none", translate("None"))
if is_finded("obfs-local") then
o:value("obfs-local", translate("obfs-local"))
end
if is_finded("v2ray-plugin") then
o:value("v2ray-plugin", translate("v2ray-plugin"))
end
if is_finded("xray-plugin") then
o:value("xray-plugin", translate("xray-plugin"))
end
o.rmempty = true
o:depends("type", "ss")
o = s:option(Value, "plugin_opts", translate("Plugin Opts"))
o.rmempty = true
o:depends({type = "ss", plugin = "obfs-local"})
o:depends({type = "ss", plugin = "v2ray-plugin"})
o:depends({type = "ss", plugin = "xray-plugin"})
o = s:option(ListValue, "protocol", translate("Protocol"))
for _, v in ipairs(protocol) do
o:value(v)
end
o.rmempty = true
o:depends("type", "ssr")
o = s:option(Value, "protocol_param", translate("Protocol param(optional)"))
o:depends("type", "ssr")
o = s:option(ListValue, "obfs", translate("Obfs"))
for _, v in ipairs(obfs) do
o:value(v)
end
o.rmempty = true
o:depends("type", "ssr")
o = s:option(Value, "obfs_param", translate("Obfs param(optional)"))
o:depends("type", "ssr")
-- VmessId
o = s:option(Value, "vmess_id", translate("Vmess/VLESS ID (UUID)"))
o.rmempty = true
o.default = uuid
o:depends({type = "v2ray", v2ray_protocol = "vmess"})
o:depends({type = "v2ray", v2ray_protocol = "vless"})
-- VLESS Encryption
o = s:option(Value, "vless_encryption", translate("VLESS Encryption"))
o.rmempty = true
o.default = "none"
o:depends({type = "v2ray", v2ray_protocol = "vless"})
-- 加密方式
o = s:option(ListValue, "security", translate("Encrypt Method"))
for _, v in ipairs(securitys) do
o:value(v, v:upper())
end
o.rmempty = true
o:depends({type = "v2ray", v2ray_protocol = "vmess"})
-- 传输协议
o = s:option(ListValue, "transport", translate("Transport"))
o:value("tcp", "TCP")
o:value("kcp", "mKCP")
o:value("ws", "WebSocket")
o:value("h2", "HTTP/2")
o:value("quic", "QUIC")
o:value("grpc", "gRPC")
o.rmempty = true
o:depends("type", "v2ray")
-- [[ TCP部分 ]]--
-- TCP伪装
o = s:option(ListValue, "tcp_guise", translate("Camouflage Type"))
o:depends("transport", "tcp")
o:value("none", translate("None"))
o:value("http", "HTTP")
o.rmempty = true
-- HTTP域名
o = s:option(Value, "http_host", translate("HTTP Host"))
o:depends("tcp_guise", "http")
o.rmempty = true
-- HTTP路径
o = s:option(Value, "http_path", translate("HTTP Path"))
o:depends("tcp_guise", "http")
o.rmempty = true
-- [[ WS部分 ]]--
-- WS域名
o = s:option(Value, "ws_host", translate("WebSocket Host"))
o:depends({transport = "ws", tls = false})
o.datatype = "hostname"
o.rmempty = true
-- WS路径
o = s:option(Value, "ws_path", translate("WebSocket Path"))
o:depends("transport", "ws")
o.rmempty = true
-- [[ H2部分 ]]--
-- H2域名
o = s:option(Value, "h2_host", translate("HTTP/2 Host"))
o:depends("transport", "h2")
o.rmempty = true
-- H2路径
o = s:option(Value, "h2_path", translate("HTTP/2 Path"))
o:depends("transport", "h2")
o.rmempty = true
-- gRPC
o = s:option(Value, "serviceName", translate("serviceName"))
o:depends("transport", "grpc")
o.rmempty = true
-- gRPC初始窗口
o = s:option(Value, "initial_windows_size", translate("Initial Windows Size"))
o.datatype = "uinteger"
o:depends("transport", "grpc")
o.default = 0
o.rmempty = true
-- H2/gRPC健康检查
o = s:option(Flag, "health_check", translate("H2/gRPC Health Check"))
o:depends("transport", "h2")
o:depends("transport", "grpc")
o.rmempty = true
o = s:option(Value, "read_idle_timeout", translate("H2 Read Idle Timeout"))
o.datatype = "uinteger"
o:depends({health_check = true, transport = "h2"})
o.default = 60
o.rmempty = true
o = s:option(Value, "idle_timeout", translate("gRPC Idle Timeout"))
o.datatype = "uinteger"
o:depends({health_check = true, transport = "grpc"})
o.default = 60
o.rmempty = true
o = s:option(Value, "health_check_timeout", translate("Health Check Timeout"))
o.datatype = "uinteger"
o:depends("health_check", 1)
o.default = 20
o.rmempty = true
o = s:option(Flag, "permit_without_stream", translate("Permit Without Stream"))
o:depends({health_check = true, transport = "grpc"})
o.rmempty = true
-- [[ QUIC部分 ]]--
o = s:option(ListValue, "quic_security", translate("QUIC Security"))
o:depends("transport", "quic")
o:value("none", translate("None"))
o:value("aes-128-gcm", translate("aes-128-gcm"))
o:value("chacha20-poly1305", translate("chacha20-poly1305"))
o.rmempty = true
o = s:option(Value, "quic_key", translate("QUIC Key"))
o:depends("transport", "quic")
o.rmempty = true
o = s:option(ListValue, "quic_guise", translate("Header"))
o:depends("transport", "quic")
o.rmempty = true
o:value("none", translate("None"))
o:value("srtp", translate("VideoCall (SRTP)"))
o:value("utp", translate("BitTorrent (uTP)"))
o:value("wechat-video", translate("WechatVideo"))
o:value("dtls", translate("DTLS 1.2"))
o:value("wireguard", translate("WireGuard"))
-- [[ mKCP部分 ]]--
o = s:option(ListValue, "kcp_guise", translate("Camouflage Type"))
o:depends("transport", "kcp")
o:value("none", translate("None"))
o:value("srtp", translate("VideoCall (SRTP)"))
o:value("utp", translate("BitTorrent (uTP)"))
o:value("wechat-video", translate("WechatVideo"))
o:value("dtls", translate("DTLS 1.2"))
o:value("wireguard", translate("WireGuard"))
o.rmempty = true
o = s:option(Value, "mtu", translate("MTU"))
o.datatype = "uinteger"
o:depends("transport", "kcp")
o.default = 1350
o.rmempty = true
o = s:option(Value, "tti", translate("TTI"))
o.datatype = "uinteger"
o:depends("transport", "kcp")
o.default = 50
o.rmempty = true
o = s:option(Value, "uplink_capacity", translate("Uplink Capacity"))
o.datatype = "uinteger"
o:depends("transport", "kcp")
o.default = 5
o.rmempty = true
o = s:option(Value, "downlink_capacity", translate("Downlink Capacity"))
o.datatype = "uinteger"
o:depends("transport", "kcp")
o.default = 20
o.rmempty = true
o = s:option(Value, "read_buffer_size", translate("Read Buffer Size"))
o.datatype = "uinteger"
o:depends("transport", "kcp")
o.default = 2
o.rmempty = true
o = s:option(Value, "write_buffer_size", translate("Write Buffer Size"))
o.datatype = "uinteger"
o:depends("transport", "kcp")
o.default = 2
o.rmempty = true
o = s:option(Value, "seed", translate("Obfuscate password (optional)"))
o:depends({v2ray_protocol = "vless", transport = "kcp"})
o.rmempty = true
o = s:option(Flag, "congestion", translate("Congestion"))
o:depends("transport", "kcp")
o.rmempty = true
-- [[ TLS ]]--
o = s:option(Flag, "tls", translate("TLS"))
o.rmempty = true
o.default = "0"
o:depends({type = "v2ray", xtls = false})
-- o:depends({type = "v2ray", v2ray_protocol = "vless", xtls = false})
o:depends("type", "trojan")
-- XTLS
if is_finded("xray") then
o = s:option(Flag, "xtls", translate("XTLS"))
o.rmempty = true
o.default = "0"
o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "tcp", tls = false})
o:depends({type = "v2ray", v2ray_protocol = "vless", transport = "kcp", tls = false})
o:depends({type = "v2ray", v2ray_protocol = "trojan", transport = "tcp", tls = false})
o:depends({type = "v2ray", v2ray_protocol = "trojan", transport = "kcp", tls = false})
end
-- Flow
o = s:option(Value, "vless_flow", translate("Flow"))
for _, v in ipairs(flows) do
o:value(v, translate(v))
end
o.rmempty = true
o.default = "xtls-rprx-splice"
o:depends("xtls", true)
-- [[ TLS部分 ]] --
o = s:option(Flag, "tls_sessionTicket", translate("Session Ticket"))
o:depends({type = "trojan", tls = true})
o.default = "0"
-- [[ uTLS ]]--
o = s:option(ListValue, "fingerprint", translate("Finger Print"))
o:value("disable", translate("disable"))
o:value("firefox", translate("firefox"))
o:value("chrome", translate("chrome"))
o:value("safari", translate("safari"))
o:value("randomized", translate("randomized"))
o:depends({type = "v2ray", tls = true})
o.default = "disable"
o = s:option(Value, "tls_host", translate("TLS Host"))
o.datatype = "hostname"
o:depends("tls", true)
o:depends("xtls", true)
o.rmempty = true
-- [[ allowInsecure ]]--
o = s:option(Flag, "insecure", translate("allowInsecure"))
o.rmempty = false
o:depends("tls", true)
o:depends("xtls", true)
o.description = translate("If true, allowss insecure connection at TLS client, e.g., TLS server uses unverifiable certificates.")
-- [[ Mux ]]--
o = s:option(Flag, "mux", translate("Mux"))
o.rmempty = false
o:depends({type = "v2ray", xtls = false})
o = s:option(Value, "concurrency", translate("Concurrency"))
o.datatype = "uinteger"
o.rmempty = true
o.default = "4"
o:depends("mux", "1")
o:depends("type", "naiveproxy")
-- [[ Cert ]]--
o = s:option(Flag, "certificate", translate("Self-signed Certificate"))
o.rmempty = true
o.default = "0"
o:depends({type = "trojan", tls = true, insecure = false})
o:depends({type = "v2ray", v2ray_protocol = "vmess", tls = true, insecure = false})
o:depends({type = "v2ray", v2ray_protocol = "vless", tls = true, insecure = false})
o:depends({type = "v2ray", v2ray_protocol = "vmess", xtls = true, insecure = false})
o:depends({type = "v2ray", v2ray_protocol = "vless", xtls = true, insecure = false})
o.description = translate("If you have a self-signed certificate,please check the box")
o = s:option(DummyValue, "upload", translate("Upload"))
o.template = "shadowsocksr/certupload"
o:depends("certificate", 1)
cert_dir = "/etc/ssl/private/"
local path
luci.http.setfilehandler(function(meta, chunk, eof)
if not fd then
if (not meta) or (not meta.name) or (not meta.file) then
return
end
fd = nixio.open(cert_dir .. meta.file, "w")
if not fd then
path = translate("Create upload file error.")
return
end
end
if chunk and fd then
fd:write(chunk)
end
if eof and fd then
fd:close()
fd = nil
path = '/etc/ssl/private/' .. meta.file .. ''
end
end)
if luci.http.formvalue("upload") then
local f = luci.http.formvalue("ulfile")
if #f <= 0 then
path = translate("No specify upload file.")
end
end
o = s:option(Value, "certpath", translate("Current Certificate Path"))
o:depends("certificate", 1)
o:value("/etc/ssl/private/ca.pem")
o.description = translate("Please confirm the current certificate path")
o.default = "/etc/ssl/private/ca.pem"
o = s:option(Flag, "fast_open", translate("TCP Fast Open"))
o.rmempty = true
o.default = "0"
o:depends("type", "ssr")
o:depends("type", "ss")
o:depends("type", "trojan")
o = s:option(Flag, "switch_enable", translate("Enable Auto Switch"))
o.rmempty = false
o.default = "1"
o = s:option(Value, "local_port", translate("Local Port"))
o.datatype = "port"
o.default = 1234
o.rmempty = false
if is_finded("kcptun-client") then
kcp_enable = s:option(Flag, "kcp_enable", translate("KcpTun Enable"))
kcp_enable.rmempty = true
kcp_enable.default = "0"
kcp_enable:depends("type", "ssr")
kcp_enable:depends("type", "ss")
o = s:option(Value, "kcp_port", translate("KcpTun Port"))
o.datatype = "port"
o.default = 4000
o:depends("type", "ssr")
o:depends("type", "ss")
o = s:option(Value, "kcp_password", translate("KcpTun Password"))
o.password = true
o:depends("type", "ssr")
o:depends("type", "ss")
o = s:option(Value, "kcp_param", translate("KcpTun Param"))
o.default = "--nocomp"
o:depends("type", "ssr")
o:depends("type", "ss")
end
return m

View File

@ -0,0 +1,112 @@
-- Copyright (C) 2017 yushi studio <ywb94@qq.com> github.com/ywb94
-- Copyright (C) 2018 lean <coolsnowwolf@gmail.com> github.com/coolsnowwolf
-- Licensed to the public under the GNU General Public License v3.
local m, s, sec, o, kcp_enable
local uci = luci.model.uci.cursor()
m = Map("shadowsocksr", translate("ShadowSocksR Plus+ Settings"))
m:section(SimpleSection).template = "shadowsocksr/status"
local server_table = {}
uci:foreach("shadowsocksr", "servers", function(s)
if s.alias then
server_table[s[".name"]] = "[%s]:%s" % {string.upper(s.v2ray_protocol or s.type), s.alias}
elseif s.server and s.server_port then
server_table[s[".name"]] = "[%s]:%s:%s" % {string.upper(s.v2ray_protocol or s.type), s.server, s.server_port}
end
end)
local key_table = {}
for key, _ in pairs(server_table) do
table.insert(key_table, key)
end
table.sort(key_table)
-- [[ Global Setting ]]--
s = m:section(TypedSection, "global")
s.anonymous = true
o = s:option(ListValue, "global_server", translate("Main Server"))
o:value("nil", translate("Disable"))
for _, key in pairs(key_table) do
o:value(key, server_table[key])
end
o.default = "nil"
o.rmempty = false
o = s:option(ListValue, "udp_relay_server", translate("Game Mode UDP Server"))
o:value("", translate("Disable"))
o:value("same", translate("Same as Global Server"))
for _, key in pairs(key_table) do
o:value(key, server_table[key])
end
if uci:get_first("shadowsocksr", 'global', 'netflix_enable', '0') ~= '0' then
o = s:option(ListValue, "netflix_server", translate("Netflix Node"))
o:value("nil", translate("Disable"))
o:value("same", translate("Same as Global Server"))
for _, key in pairs(key_table) do
o:value(key, server_table[key])
end
o.default = "nil"
o.rmempty = false
o = s:option(Flag, "netflix_proxy", translate("External Proxy Mode"))
o.rmempty = false
o.description = translate("Forward Netflix Proxy through Main Proxy")
o.default = "0"
end
o = s:option(ListValue, "threads", translate("Multi Threads Option"))
o:value("0", translate("Auto Threads"))
o:value("1", translate("1 Thread"))
o:value("2", translate("2 Threads"))
o:value("4", translate("4 Threads"))
o:value("8", translate("8 Threads"))
o:value("16", translate("16 Threads"))
o:value("32", translate("32 Threads"))
o:value("64", translate("64 Threads"))
o:value("128", translate("128 Threads"))
o.default = "0"
o.rmempty = false
o = s:option(ListValue, "run_mode", translate("Running Mode"))
o:value("gfw", translate("GFW List Mode"))
o:value("router", translate("IP Route Mode"))
o:value("all", translate("Global Mode"))
o:value("oversea", translate("Oversea Mode"))
o.default = gfw
o = s:option(ListValue, "dports", translate("Proxy Ports"))
o:value("1", translate("All Ports"))
o:value("2", translate("Only Common Ports"))
o.default = 1
o = s:option(ListValue, "pdnsd_enable", translate("Resolve Dns Mode"))
o:value("1", translate("Use Pdnsd tcp query and cache"))
o:value("2", translate("Use DNS2SOCKS query and cache"))
o:value("0", translate("Use Local DNS Service listen port 5335"))
o.default = 1
o = s:option(Value, "tunnel_forward", translate("Anti-pollution DNS Server"))
o:value("8.8.4.4:53", translate("Google Public DNS (8.8.4.4)"))
o:value("8.8.8.8:53", translate("Google Public DNS (8.8.8.8)"))
o:value("208.67.222.222:53", translate("OpenDNS (208.67.222.222)"))
o:value("208.67.220.220:53", translate("OpenDNS (208.67.220.220)"))
o:value("209.244.0.3:53", translate("Level 3 Public DNS (209.244.0.3)"))
o:value("209.244.0.4:53", translate("Level 3 Public DNS (209.244.0.4)"))
o:value("4.2.2.1:53", translate("Level 3 Public DNS (4.2.2.1)"))
o:value("4.2.2.2:53", translate("Level 3 Public DNS (4.2.2.2)"))
o:value("4.2.2.3:53", translate("Level 3 Public DNS (4.2.2.3)"))
o:value("4.2.2.4:53", translate("Level 3 Public DNS (4.2.2.4)"))
o:value("1.1.1.1:53", translate("Cloudflare DNS (1.1.1.1)"))
o:value("114.114.114.114:53", translate("Oversea Mode DNS-1 (114.114.114.114)"))
o:value("114.114.115.115:53", translate("Oversea Mode DNS-2 (114.114.115.115)"))
o:depends("pdnsd_enable", "1")
o:depends("pdnsd_enable", "2")
o.description = translate("Custom DNS Server format as IP:PORT (default: 8.8.4.4:53)")
o.datatype = "hostport"
return m

View File

@ -0,0 +1,143 @@
require "luci.ip"
require "nixio.fs"
local m, s, o
m = Map("shadowsocksr")
s = m:section(TypedSection, "access_control")
s.anonymous = true
-- Interface control
s:tab("Interface", translate("Interface control"))
o = s:taboption("Interface", DynamicList, "Interface", translate("Interface"))
o.template = "cbi/network_netlist"
o.widget = "checkbox"
o.nocreate = true
o.unspecified = true
o.description = translate("Listen only on the given interface or, if unspecified, on all")
-- Part of WAN
s:tab("wan_ac", translate("WAN IP AC"))
o = s:taboption("wan_ac", DynamicList, "wan_bp_ips", translate("WAN White List IP"))
o.datatype = "ip4addr"
o = s:taboption("wan_ac", DynamicList, "wan_fw_ips", translate("WAN Force Proxy IP"))
o.datatype = "ip4addr"
-- Part of LAN
s:tab("lan_ac", translate("LAN IP AC"))
o = s:taboption("lan_ac", ListValue, "lan_ac_mode", translate("LAN Access Control"))
o:value("0", translate("Disable"))
o:value("w", translate("Allow listed only"))
o:value("b", translate("Allow all except listed"))
o.rmempty = false
o = s:taboption("lan_ac", DynamicList, "lan_ac_ips", translate("LAN Host List"))
o.datatype = "ipaddr"
luci.ip.neighbors({family = 4}, function(entry)
if entry.reachable then
o:value(entry.dest:string())
end
end)
o:depends("lan_ac_mode", "w")
o:depends("lan_ac_mode", "b")
o = s:taboption("lan_ac", DynamicList, "lan_bp_ips", translate("LAN Bypassed Host List"))
o.datatype = "ipaddr"
luci.ip.neighbors({family = 4}, function(entry)
if entry.reachable then
o:value(entry.dest:string())
end
end)
o = s:taboption("lan_ac", DynamicList, "lan_fp_ips", translate("LAN Force Proxy Host List"))
o.datatype = "ipaddr"
luci.ip.neighbors({family = 4}, function(entry)
if entry.reachable then
o:value(entry.dest:string())
end
end)
o = s:taboption("lan_ac", DynamicList, "lan_gm_ips", translate("Game Mode Host List"))
o.datatype = "ipaddr"
luci.ip.neighbors({family = 4}, function(entry)
if entry.reachable then
o:value(entry.dest:string())
end
end)
-- Part of Self
-- s:tab("self_ac", translate("Router Self AC"))
-- o = s:taboption("self_ac",ListValue, "router_proxy", translate("Router Self Proxy"))
-- o:value("1", translatef("Normal Proxy"))
-- o:value("0", translatef("Bypassed Proxy"))
-- o:value("2", translatef("Forwarded Proxy"))
-- o.rmempty = false
s:tab("esc", translate("Bypass Domain List"))
local escconf = "/etc/ssrplus/white.list"
o = s:taboption("esc", TextValue, "escconf")
o.rows = 13
o.wrap = "off"
o.rmempty = true
o.cfgvalue = function(self, section)
return nixio.fs.readfile(escconf) or ""
end
o.write = function(self, section, value)
nixio.fs.writefile(escconf, value:gsub("\r\n", "\n"))
end
o.remove = function(self, section, value)
nixio.fs.writefile(escconf, "")
end
s:tab("block", translate("Black Domain List"))
local blockconf = "/etc/ssrplus/black.list"
o = s:taboption("block", TextValue, "blockconf")
o.rows = 13
o.wrap = "off"
o.rmempty = true
o.cfgvalue = function(self, section)
return nixio.fs.readfile(blockconf) or " "
end
o.write = function(self, section, value)
nixio.fs.writefile(blockconf, value:gsub("\r\n", "\n"))
end
o.remove = function(self, section, value)
nixio.fs.writefile(blockconf, "")
end
s:tab("denydomain", translate("Deny Domain List"))
local denydomainconf = "/etc/ssrplus/deny.list"
o = s:taboption("denydomain", TextValue, "denydomainconf")
o.rows = 13
o.wrap = "off"
o.rmempty = true
o.cfgvalue = function(self, section)
return nixio.fs.readfile(denydomainconf) or " "
end
o.write = function(self, section, value)
nixio.fs.writefile(denydomainconf, value:gsub("\r\n", "\n"))
end
o.remove = function(self, section, value)
nixio.fs.writefile(denydomainconf, "")
end
s:tab("netflix", translate("Netflix Domain List"))
local netflixconf = "/etc/ssrplus/netflix.list"
o = s:taboption("netflix", TextValue, "netflixconf")
o.rows = 13
o.wrap = "off"
o.rmempty = true
o.cfgvalue = function(self, section)
return nixio.fs.readfile(netflixconf) or " "
end
o.write = function(self, section, value)
nixio.fs.writefile(netflixconf, value:gsub("\r\n", "\n"))
end
o.remove = function(self, section, value)
nixio.fs.writefile(netflixconf, "")
end
return m

View File

@ -0,0 +1,20 @@
require "luci.util"
require "nixio.fs"
f = SimpleForm("logview")
f.reset = false
f.submit = false
t = f:field(TextValue, "conf")
t.rmempty = true
t.rows = 20
function t.cfgvalue()
if nixio.fs.access("/var/log/ssrplus.log") then
local logs = luci.util.execi("cat /var/log/ssrplus.log")
local s = ""
for line in logs do
s = line .. "\n" .. s
end
return s
end
end
t.readonly = "readonly"
return f

View File

@ -0,0 +1,150 @@
-- Copyright (C) 2017 yushi studio <ywb94@qq.com>
-- Licensed to the public under the GNU General Public License v3.
require "luci.http"
require "luci.dispatcher"
require "nixio.fs"
local m, s, o
local sid = arg[1]
local encrypt_methods = {
"rc4-md5",
"rc4-md5-6",
"rc4",
"table",
"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",
"rc2-cfb",
"seed-cfb",
"salsa20",
"chacha20",
"chacha20-ietf"
}
local encrypt_methods_ss = {
-- aead
"aes-128-gcm",
"aes-192-gcm",
"aes-256-gcm",
"chacha20-ietf-poly1305",
"xchacha20-ietf-poly1305"
--[[ stream
"table",
"rc4",
"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" ]]
}
local protocol = {"origin"}
obfs = {"plain", "http_simple", "http_post"}
m = Map("shadowsocksr", translate("Edit ShadowSocksR Server"))
m.redirect = luci.dispatcher.build_url("admin/services/shadowsocksr/server")
if m.uci:get("shadowsocksr", sid) ~= "server_config" then
luci.http.redirect(m.redirect)
return
end
-- [[ Server Setting ]]--
s = m:section(NamedSection, sid, "server_config")
s.anonymous = true
s.addremove = false
o = s:option(Flag, "enable", translate("Enable"))
o.default = 1
o.rmempty = false
o = s:option(ListValue, "type", translate("Server Type"))
o:value("socks5", translate("Socks5"))
if nixio.fs.access("/usr/bin/ssserver") or nixio.fs.access("/usr/bin/ss-server") then
o:value("ss", translate("Shadowsocks"))
end
if nixio.fs.access("/usr/bin/ssr-server") then
o:value("ssr", translate("ShadowsocksR"))
end
o.default = "socks5"
o = s:option(Value, "server_port", translate("Server Port"))
o.datatype = "port"
math.randomseed(tostring(os.time()):reverse():sub(1, 7))
o.default = math.random(10240, 20480)
o.rmempty = false
o.description = translate("warning! Please do not reuse the port!")
o = s:option(Value, "timeout", translate("Connection Timeout"))
o.datatype = "uinteger"
o.default = 60
o.rmempty = false
o:depends("type", "ss")
o:depends("type", "ssr")
o = s:option(Value, "username", translate("Username"))
o.rmempty = false
o:depends("type", "socks5")
o = s:option(Value, "password", translate("Password"))
o.password = true
o.rmempty = false
o = s:option(ListValue, "encrypt_method", translate("Encrypt Method"))
for _, v in ipairs(encrypt_methods) do
o:value(v)
end
o.rmempty = false
o:depends("type", "ssr")
o = s:option(ListValue, "encrypt_method_ss", translate("Encrypt Method"))
for _, v in ipairs(encrypt_methods_ss) do
o:value(v)
end
o.rmempty = false
o:depends("type", "ss")
o = s:option(ListValue, "protocol", translate("Protocol"))
for _, v in ipairs(protocol) do
o:value(v)
end
o.rmempty = false
o:depends("type", "ssr")
o = s:option(ListValue, "obfs", translate("Obfs"))
for _, v in ipairs(obfs) do
o:value(v)
end
o.rmempty = false
o:depends("type", "ssr")
o = s:option(Value, "obfs_param", translate("Obfs param(optional)"))
o:depends("type", "ssr")
o = s:option(Flag, "fast_open", translate("TCP Fast Open"))
o.rmempty = false
o:depends("type", "ss")
o:depends("type", "ssr")
return m

View File

@ -0,0 +1,140 @@
-- Copyright (C) 2017 yushi studio <ywb94@qq.com>
-- Licensed to the public under the GNU General Public License v3.
require "luci.http"
require "luci.dispatcher"
local m, sec, o
local encrypt_methods = {
"table",
"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",
"rc2-cfb",
"seed-cfb",
"salsa20",
"chacha20",
"chacha20-ietf"
}
local encrypt_methods_ss = {
-- aead
"aes-128-gcm",
"aes-192-gcm",
"aes-256-gcm",
"chacha20-ietf-poly1305",
"xchacha20-ietf-poly1305"
--[[ stream
"table",
"rc4",
"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" ]]
}
local protocol = {
"origin",
"verify_deflate",
"auth_sha1_v4",
"auth_aes128_sha1",
"auth_aes128_md5",
"auth_chain_a"
}
obfs = {
"plain",
"http_simple",
"http_post",
"random_head",
"tls1.2_ticket_auth",
"tls1.2_ticket_fastauth"
}
m = Map("shadowsocksr")
-- [[ Global Setting ]]--
sec = m:section(TypedSection, "server_global", translate("Global Setting"))
sec.anonymous = true
o = sec:option(Flag, "enable_server", translate("Enable Server"))
o.rmempty = false
-- [[ Server Setting ]]--
sec = m:section(TypedSection, "server_config", translate("Server Setting"))
sec.anonymous = true
sec.addremove = true
sec.template = "cbi/tblsection"
sec.extedit = luci.dispatcher.build_url("admin/services/shadowsocksr/server/%s")
function sec.create(...)
local sid = TypedSection.create(...)
if sid then
luci.http.redirect(sec.extedit % sid)
return
end
end
o = sec:option(Flag, "enable", translate("Enable"))
function o.cfgvalue(...)
return Value.cfgvalue(...) or translate("0")
end
o.rmempty = false
o = sec:option(DummyValue, "type", translate("Server Type"))
function o.cfgvalue(...)
return Value.cfgvalue(...) or "ss"
end
o = sec:option(DummyValue, "server_port", translate("Server Port"))
function o.cfgvalue(...)
return Value.cfgvalue(...) or "-"
end
o = sec:option(DummyValue, "username", translate("Username"))
function o.cfgvalue(...)
return Value.cfgvalue(...) or "-"
end
o = sec:option(DummyValue, "encrypt_method", translate("Encrypt Method"))
function o.cfgvalue(...)
local v = Value.cfgvalue(...)
return v and v:upper() or "-"
end
o = sec:option(DummyValue, "encrypt_method_ss", translate("Encrypt Method"))
function o.cfgvalue(...)
local v = Value.cfgvalue(...)
return v and v:upper() or "-"
end
o = sec:option(DummyValue, "protocol", translate("Protocol"))
function o.cfgvalue(...)
return Value.cfgvalue(...) or "-"
end
o = sec:option(DummyValue, "obfs", translate("Obfs"))
function o.cfgvalue(...)
return Value.cfgvalue(...) or "-"
end
return m

View File

@ -0,0 +1,151 @@
-- Licensed to the public under the GNU General Public License v3.
require "luci.http"
require "luci.dispatcher"
require "luci.model.uci"
local m, s, o
local uci = luci.model.uci.cursor()
local server_count = 0
uci:foreach("shadowsocksr", "servers", function(s)
server_count = server_count + 1
end)
m = Map("shadowsocksr", translate("Servers subscription and manage"))
-- Server Subscribe
s = m:section(TypedSection, "server_subscribe")
s.anonymous = true
o = s:option(Flag, "auto_update", translate("Auto Update"))
o.rmempty = false
o.description = translate("Auto Update Server subscription, GFW list and CHN route")
o = s:option(ListValue, "auto_update_time", translate("Update time (every day)"))
for t = 0, 23 do
o:value(t, t .. ":00")
end
o.default = 2
o.rmempty = false
o = s:option(DynamicList, "subscribe_url", translate("Subscribe URL"))
o.rmempty = true
o = s:option(Value, "filter_words", translate("Subscribe Filter Words"))
o.rmempty = true
o.description = translate("Filter Words splited by /")
o = s:option(Value, "save_words", translate("Subscribe Save Words"))
o.rmempty = true
o.description = translate("Save Words splited by /")
o = s:option(Button, "update_Sub", translate("Update Subscribe List"))
o.inputstyle = "reload"
o.description = translate("Update subscribe url list first")
o.write = function()
uci:commit("shadowsocksr")
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr", "servers"))
end
o = s:option(Flag, "switch", translate("Subscribe Default Auto-Switch"))
o.rmempty = false
o.description = translate("Subscribe new add server default Auto-Switch on")
o.default = "1"
o = s:option(Flag, "proxy", translate("Through proxy update"))
o.rmempty = false
o.description = translate("Through proxy update list, Not Recommended ")
o = s:option(Button, "subscribe", translate("Update All Subscribe Severs"))
o.rawhtml = true
o.template = "shadowsocksr/subscribe"
o = s:option(Button, "delete", translate("Delete All Subscribe Severs"))
o.inputstyle = "reset"
o.description = string.format(translate("Server Count") .. ": %d", server_count)
o.write = function()
uci:delete_all("shadowsocksr", "servers", function(s)
if s.hashkey or s.isSubscribe then
return true
else
return false
end
end)
uci:save("shadowsocksr")
uci:commit("shadowsocksr")
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr", "delete"))
return
end
-- [[ Servers Manage ]]--
s = m:section(TypedSection, "servers")
s.anonymous = true
s.addremove = true
s.template = "cbi/tblsection"
s.sortable = true
s.extedit = luci.dispatcher.build_url("admin", "services", "shadowsocksr", "servers", "%s")
function s.create(...)
local sid = TypedSection.create(...)
if sid then
luci.http.redirect(s.extedit % sid)
return
end
end
o = s:option(DummyValue, "type", translate("Type"))
function o.cfgvalue(self, section)
return m:get(section, "v2ray_protocol") or Value.cfgvalue(self, section) or translate("None")
end
o = s:option(DummyValue, "alias", translate("Alias"))
function o.cfgvalue(...)
return Value.cfgvalue(...) or translate("None")
end
o = s:option(DummyValue, "server_port", translate("Server Port"))
function o.cfgvalue(...)
return Value.cfgvalue(...) or "N/A"
end
o = s:option(DummyValue, "server_port", translate("Socket Connected"))
o.template = "shadowsocksr/socket"
o.width = "10%"
o.render = function(self, section, scope)
self.transport = s:cfgvalue(section).transport
if self.transport == 'ws' then
self.ws_path = s:cfgvalue(section).ws_path
self.tls = s:cfgvalue(section).tls
end
DummyValue.render(self, section, scope)
end
o = s:option(DummyValue, "server", translate("Ping Latency"))
o.template = "shadowsocksr/ping"
o.width = "10%"
local global_server = uci:get_first('shadowsocksr', 'global', 'global_server')
node = s:option(Button, "apply_node", translate("Apply"))
node.inputstyle = "apply"
node.render = function(self, section, scope)
if section == global_server then
self.title = translate("Reapply")
else
self.title = translate("Apply")
end
Button.render(self, section, scope)
end
node.write = function(self, section)
uci:set("shadowsocksr", '@global[0]', 'global_server', section)
uci:save("shadowsocksr")
uci:commit("shadowsocksr")
luci.http.redirect(luci.dispatcher.build_url("admin", "services", "shadowsocksr", "restart"))
end
o = s:option(Flag, "switch_enable", translate("Auto Switch"))
o.rmempty = false
function o.cfgvalue(...)
return Value.cfgvalue(...) or 1
end
m:append(Template("shadowsocksr/server_list"))
return m

View File

@ -0,0 +1,195 @@
-- Copyright (C) 2017 yushi studio <ywb94@qq.com>
-- Licensed to the public under the GNU General Public License v3.
require "nixio.fs"
require "luci.sys"
require "luci.model.uci"
local m, s, o
local redir_run = 0
local reudp_run = 0
local sock5_run = 0
local server_run = 0
local kcptun_run = 0
local tunnel_run = 0
local gfw_count = 0
local ad_count = 0
local ip_count = 0
local nfip_count = 0
local Process_list = luci.sys.exec("busybox ps -w")
local uci = luci.model.uci.cursor()
-- html constants
font_blue = [[<b style=color:green>]]
style_blue = [[<b style=color:red>]]
font_off = [[</b>]]
bold_on = [[<strong>]]
bold_off = [[</strong>]]
local kcptun_version = translate("Unknown")
local kcp_file = "/usr/bin/kcptun-client"
if not nixio.fs.access(kcp_file) then
kcptun_version = translate("Not exist")
else
if not nixio.fs.access(kcp_file, "rwx", "rx", "rx") then
nixio.fs.chmod(kcp_file, 755)
end
kcptun_version = "<b>" ..luci.sys.exec(kcp_file .. " -v | awk '{printf $3}'") .. "</b>"
if not kcptun_version or kcptun_version == "" then
kcptun_version = translate("Unknown")
end
end
if nixio.fs.access("/etc/ssrplus/gfw_list.conf") then
gfw_count = tonumber(luci.sys.exec("cat /etc/ssrplus/gfw_list.conf | wc -l")) / 2
end
if nixio.fs.access("/etc/ssrplus/ad.conf") then
ad_count = tonumber(luci.sys.exec("cat /etc/ssrplus/ad.conf | wc -l"))
end
if nixio.fs.access("/etc/ssrplus/china_ssr.txt") then
ip_count = tonumber(luci.sys.exec("cat /etc/ssrplus/china_ssr.txt | wc -l"))
end
if nixio.fs.access("/etc/ssrplus/netflixip.list") then
nfip_count = tonumber(luci.sys.exec("cat /etc/ssrplus/netflixip.list | wc -l"))
end
if Process_list:find("udp.only.ssr.reudp") then
reudp_run = 1
end
if Process_list:find("tcp.only.ssr.retcp") then
redir_run = 1
end
if Process_list:find("tcp.udp.ssr.local") then
sock5_run = 1
end
if Process_list:find("tcp.udp.ssr.retcp") then
redir_run = 1
reudp_run = 1
end
if Process_list:find("local.ssr.retcp") then
redir_run = 1
sock5_run = 1
end
if Process_list:find("local.udp.ssr.retcp") then
reudp_run = 1
redir_run = 1
sock5_run = 1
end
if Process_list:find("kcptun.client") then
kcptun_run = 1
end
if Process_list:find("ssr.server") then
server_run = 1
end
if Process_list:find("ssrplus/bin/pdnsd") or (Process_list:find("ssrplus.dns") and Process_list:find("dns2socks.127.0.0.1.*127.0.0.1.5335")) then
pdnsd_run = 1
end
m = SimpleForm("Version")
m.reset = false
m.submit = false
s = m:field(DummyValue, "redir_run", translate("Global Client"))
s.rawhtml = true
if redir_run == 1 then
s.value = font_blue .. bold_on .. translate("Running") .. bold_off .. font_off
else
s.value = style_blue .. bold_on .. translate("Not Running") .. bold_off .. font_off
end
s = m:field(DummyValue, "reudp_run", translate("Game Mode UDP Relay"))
s.rawhtml = true
if reudp_run == 1 then
s.value = font_blue .. bold_on .. translate("Running") .. bold_off .. font_off
else
s.value = style_blue .. bold_on .. translate("Not Running") .. bold_off .. font_off
end
if uci:get_first("shadowsocksr", 'global', 'pdnsd_enable', '0') ~= '0' then
s = m:field(DummyValue, "pdnsd_run", translate("DNS Anti-pollution"))
s.rawhtml = true
if pdnsd_run == 1 then
s.value = font_blue .. bold_on .. translate("Running") .. bold_off .. font_off
else
s.value = style_blue .. bold_on .. translate("Not Running") .. bold_off .. font_off
end
end
s = m:field(DummyValue, "sock5_run", translate("Global SOCKS5 Proxy Server"))
s.rawhtml = true
if sock5_run == 1 then
s.value = font_blue .. bold_on .. translate("Running") .. bold_off .. font_off
else
s.value = style_blue .. bold_on .. translate("Not Running") .. bold_off .. font_off
end
s = m:field(DummyValue, "server_run", translate("Local Servers"))
s.rawhtml = true
if server_run == 1 then
s.value = font_blue .. bold_on .. translate("Running") .. bold_off .. font_off
else
s.value = style_blue .. bold_on .. translate("Not Running") .. bold_off .. font_off
end
if nixio.fs.access("/usr/bin/kcptun-client") then
s = m:field(DummyValue, "kcp_version", translate("KcpTun Version"))
s.rawhtml = true
s.value = kcptun_version
s = m:field(DummyValue, "kcptun_run", translate("KcpTun"))
s.rawhtml = true
if kcptun_run == 1 then
s.value = font_blue .. bold_on .. translate("Running") .. bold_off .. font_off
else
s.value = style_blue .. bold_on .. translate("Not Running") .. bold_off .. font_off
end
end
s = m:field(DummyValue, "google", translate("Google Connectivity"))
s.value = translate("No Check")
s.template = "shadowsocksr/check"
s = m:field(DummyValue, "baidu", translate("Baidu Connectivity"))
s.value = translate("No Check")
s.template = "shadowsocksr/check"
s = m:field(DummyValue, "gfw_data", translate("GFW List Data"))
s.rawhtml = true
s.template = "shadowsocksr/refresh"
s.value = gfw_count .. " " .. translate("Records")
s = m:field(DummyValue, "ip_data", translate("China IP Data"))
s.rawhtml = true
s.template = "shadowsocksr/refresh"
s.value = ip_count .. " " .. translate("Records")
if uci:get_first("shadowsocksr", 'global', 'netflix_enable', '0') ~= '0' then
s = m:field(DummyValue, "nfip_data", translate("Netflix IP Data"))
s.rawhtml = true
s.template = "shadowsocksr/refresh"
s.value = nfip_count .. " " .. translate("Records")
end
if uci:get_first("shadowsocksr", 'global', 'adblock', '0') == '1' then
s = m:field(DummyValue, "ad_data", translate("Advertising Data"))
s.rawhtml = true
s.template = "shadowsocksr/refresh"
s.value = ad_count .. " " .. translate("Records")
end
if uci:get_first("shadowsocksr", 'global', 'pdnsd_enable', '0') == '1' then
s = m:field(DummyValue, "cache", translate("Reset pdnsd cache"))
s.template = "shadowsocksr/cache"
end
s = m:field(DummyValue, "check_port", translate("Check Server Port"))
s.template = "shadowsocksr/checkport"
s.value = translate("No Check")
return m

View File

@ -0,0 +1,29 @@
<%+cbi/valueheader%>
<script type="text/javascript">//<![CDATA[
function cache(btn,urlname)
{
btn.disabled = true;
btn.value = '<%:Perform reset%>';
murl=urlname;
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "shadowsocksr","cache")%>',
{ set:murl },
function(x,rv)
{
var s = document.getElementById(urlname+'-status');
if (s)
{
if (rv.ret=="0")
s.innerHTML ="<font color='green'>"+"<%:Reset complete%>"+"</font>";
else
s.innerHTML ="<font color='red'>"+"<%:Reset Error%>"+"</font>";
}
btn.disabled = false;
btn.value = '<%:Perform reset%>';
}
);
return false;
}
//]]></script>
<input type="button" class="btn cbi-button cbi-button-reset" value="<%:Perform reset%>" onclick="return cache(this,'<%=self.option%>')" />
<span id="<%=self.option%>-status"><em><%=self.value%></em></span>
<%+cbi/valuefooter%>

Some files were not shown because too many files have changed in this diff Show More