update 2026-08-08 00:47:38

This commit is contained in:
action
2026-08-08 00:47:38 +08:00
parent 8bad7f08b8
commit 9298d4285a
38 changed files with 1289 additions and 395 deletions
@@ -89,9 +89,8 @@ if has_xray or has_singbox then
o.rmempty = false
o.description = "<ul>"
.. "<li>" .. translate("Experimental feature.") .. "</li>"
.. "<li>" .. "1." .. translate("Analyzes and preloads GeoIP/Geosite data to enhance the shunt performance of Sing-box/Xray.") .. "</li>"
.. "<li>" .. "1." .. translate("Parses and preloads GeoIP/Geosite data to improve Sing-box/Xray routing performance.") .. "</li>"
.. "<li>" .. "2." .. translate("Once enabled, the rule list can support GeoIP/Geosite rules.") .. "</li>"
.. "<li>" .. translate("Note: Increases resource usage; Geosite analysis is only supported in ChinaDNS-NG and SmartDNS modes.") .. "</li>"
.. "</ul>"
function o.write(self, section, value)
local old = m:get(section, self.option) or "0"
+2 -5
View File
@@ -1085,15 +1085,12 @@ msgstr "启用此选项后手动更新时,即使没有新版本也会从现有 G
msgid "Enable Geo Data Parsing"
msgstr "开启 Geo 数据解析"
msgid "Analyzes and preloads GeoIP/Geosite data to enhance the shunt performance of Sing-box/Xray."
msgstr "析和预加载 GeoIP/Geosite 数据,以增强 Sing-box/Xray 的分流效果。"
msgid "Parses and preloads GeoIP/Geosite data to improve Sing-box/Xray routing performance."
msgstr "析和预加载 GeoIP/Geosite 数据,以增强 Sing-box/Xray 的分流效果。"
msgid "Once enabled, the rule list can support GeoIP/Geosite rules."
msgstr "启用后,规则列表可以支持 GeoIP/Geosite 规则。"
msgid "Note: Increases resource usage; Geosite analysis is only supported in ChinaDNS-NG and SmartDNS modes."
msgstr "注:会增加一些系统资源的开销,仅在 ChinaDNS-NG 和 SmartDNS 模式下支持分析 Geosite 。"
msgid "Shunt Rule"
msgstr "分流规则"
@@ -227,7 +227,7 @@ if USE_DIRECT_LIST == "1" and not fs.access(file_direct_host) then
geosite_arg = geosite_arg .. (geosite_arg ~= "" and "," or "") .. line
else
line = api.get_std_domain(line)
if line ~= "" and not line:find("#") then
if line ~= "" and not line:find("#") and not line:find(":") then
insert_unique(direct_domain, line, lookup_direct_domain)
end
end
@@ -277,7 +277,7 @@ if USE_PROXY_LIST == "1" and not fs.access(file_proxy_host) then
geosite_arg = geosite_arg .. (geosite_arg ~= "" and "," or "") .. line
else
line = api.get_std_domain(line)
if line ~= "" and not line:find("#") then
if line ~= "" and not line:find("#") and not line:find(":") then
insert_unique(proxy_domain, line, lookup_proxy_domain)
end
end
@@ -398,7 +398,7 @@ if IS_SHUNT_NODE then
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("ext:") then
if line ~= "" and not line:find("#") and not line:find("regexp:") and not line:find("ext:") and not line:find("rule-set:") and not line:find("rs:") then
if line:find("geosite:") then
line = string.match(line, ":([^:]+)$")
if _node_id == "_direct" then
@@ -181,10 +181,15 @@ function add_rule(var)
local CACHE_TEXT_FILE = CACHE_DNS_PATH .. ".txt"
local USE_CHINADNS_NG = "0"
local IS_SHUNT_NODE = uci:get(appname, TCP_NODE, "protocol") == "_shunt"
local USE_GEOVIEW = uci:get(appname, "@global_rules[0]", "enable_geoview")
local list1 = {}
local excluded_domain = {}
if not api.is_finded("geoview") then
USE_GEOVIEW = "0"
end
local function log(...)
if NO_LOGIC_LOG == "1" then
return
@@ -301,6 +306,21 @@ function add_rule(var)
return false
end
local function foreach_geosite(list_arg, callback)
local geosite_path = uci:get(appname, "@global_rules[0]", "v2ray_location_asset") or "/usr/share/v2ray/"
geosite_path = geosite_path:match("^(.*)/") .. "/geosite.dat"
if not fs.access(geosite_path) then return end
local bin = api.finded_com("geoview")
if not (bin and list_arg) then return end
local cmd = string.format("%q -type geosite -action extract -input %q -list %q -lowmem=true", bin, geosite_path, list_arg)
local pipe = io.popen(cmd)
if not pipe then return end
for line in pipe:lines() do
if line ~= "" then callback(line) end
end
pipe:close()
end
local cache_text = ""
local nodes_address_md5 = sys.exec("echo -n $(uci show passwall | grep '\\.address') | md5sum")
local new_rules = sys.exec("echo -n $(find /usr/share/passwall/rules -type f | xargs md5sum)")
@@ -347,16 +367,28 @@ function add_rule(var)
--屏蔽列表
if USE_CHINADNS_NG == "0" and USE_BLOCK_LIST == "1" then
local geosite_arg = ""
local f = io.open("/usr/share/passwall/rules/block_host")
if f then
for line in f:lines() do
line = api.get_std_domain(line)
if line ~= "" and not line:find("#") and not line:find(":") then
set_domain_address(line, "")
if not line:find("#") and line:find("geosite:") then
line = string.match(line, ":([^:]+)$")
geosite_arg = geosite_arg .. (geosite_arg ~= "" and "," or "") .. line
else
line = api.get_std_domain(line)
if line ~= "" and not line:find("#") and not line:find(":") then
set_domain_address(line, "")
end
end
end
f:close()
end
if USE_GEOVIEW == "1" and geosite_arg ~= "" then
foreach_geosite(geosite_arg, function(line)
set_domain_address(line, "")
end)
log(" - 解析[屏蔽列表] Geosite 到屏蔽域名表(blocklist)完成")
end
end
local fwd_dns
@@ -400,88 +432,124 @@ function add_rule(var)
--直连(白名单)列表
if USE_DIRECT_LIST == "1" then
if fs.access("/usr/share/passwall/rules/direct_host") then
fwd_dns = LOCAL_DNS
if USE_CHINADNS_NG == "1" then
fwd_dns = nil
end
if fwd_dns then
local sets = {
setflag_4 .. "psw_white",
setflag_6 .. "psw_white6"
}
--始终用国内DNS解析直连(白名单)列表
for line in io.lines("/usr/share/passwall/rules/direct_host") do
line = api.get_std_domain(line)
if line ~= "" and not line:find("#") and not line:find(":") then
add_excluded_domain(line)
set_domain_dns(line, fwd_dns)
set_domain_ipset(line, table.concat(sets, ","))
fwd_dns = LOCAL_DNS
if USE_CHINADNS_NG == "1" then
fwd_dns = nil
end
if fwd_dns then
local sets = {
setflag_4 .. "psw_white",
setflag_6 .. "psw_white6"
}
--始终用国内DNS解析直连(白名单)列表
local geosite_arg = ""
local f = io.open("/usr/share/passwall/rules/direct_host")
if f then
for line in f:lines() do
if not line:find("#") and line:find("geosite:") then
line = string.match(line, ":([^:]+)$")
geosite_arg = geosite_arg .. (geosite_arg ~= "" and "," or "") .. line
else
line = api.get_std_domain(line)
if line ~= "" and not line:find("#") and not line:find(":") then
add_excluded_domain(line)
set_domain_dns(line, fwd_dns)
set_domain_ipset(line, table.concat(sets, ","))
end
end
end
f:close()
log(string.format(" - 域名白名单(whitelist)%s", fwd_dns or "默认"))
end
if USE_GEOVIEW == "1" and geosite_arg ~= "" then
foreach_geosite(geosite_arg, function(line)
add_excluded_domain(line)
set_domain_dns(line, fwd_dns)
set_domain_ipset(line, table.concat(sets, ","))
end)
log(" - 解析[直连列表] Geosite 到域名白名单(whitelist)完成")
end
end
end
--代理(黑名单)列表
if USE_PROXY_LIST == "1" then
if fs.access("/usr/share/passwall/rules/proxy_host") then
fwd_dns = TUN_DNS
if USE_CHINADNS_NG == "1" then
fwd_dns = nil
fwd_dns = TUN_DNS
if USE_CHINADNS_NG == "1" then
fwd_dns = nil
end
if fwd_dns then
local set_name = "psw_black"
local set6_name = "psw_black6"
if FLAG ~= "default" then
set_name = "psw_" .. FLAG .. "_black"
set6_name = "psw_" .. FLAG .. "_black6"
end
if fwd_dns then
local set_name = "psw_black"
local set6_name = "psw_black6"
if FLAG ~= "default" then
set_name = "psw_" .. FLAG .. "_black"
set6_name = "psw_" .. FLAG .. "_black6"
end
local sets = {
setflag_4 .. set_name
}
if NO_PROXY_IPV6 ~= "1" then
table.insert(sets, setflag_6 .. set6_name)
end
--始终使用远程DNS解析代理(黑名单)列表
for line in io.lines("/usr/share/passwall/rules/proxy_host") do
line = api.get_std_domain(line)
if line ~= "" and not line:find("#") and not line:find(":") then
add_excluded_domain(line)
if NO_PROXY_IPV6 == "1" then
set_domain_address(line, "::")
local sets = {
setflag_4 .. set_name
}
if NO_PROXY_IPV6 ~= "1" then
table.insert(sets, setflag_6 .. set6_name)
end
--始终使用远程DNS解析代理(黑名单)列表
local geosite_arg = ""
local f = io.open("/usr/share/passwall/rules/proxy_host")
if f then
for line in f:lines() do
if not line:find("#") and line:find("geosite:") then
line = string.match(line, ":([^:]+)$")
geosite_arg = geosite_arg .. (geosite_arg ~= "" and "," or "") .. line
else
line = api.get_std_domain(line)
if line ~= "" and not line:find("#") and not line:find(":") then
add_excluded_domain(line)
if NO_PROXY_IPV6 == "1" then
set_domain_address(line, "::")
end
set_domain_dns(line, fwd_dns)
set_domain_ipset(line, table.concat(sets, ","))
end
set_domain_dns(line, fwd_dns)
set_domain_ipset(line, table.concat(sets, ","))
end
end
f:close()
log(string.format(" - 代理域名表(blacklist)%s", fwd_dns or "默认"))
end
if USE_GEOVIEW == "1" and geosite_arg ~= "" then
foreach_geosite(geosite_arg, function(line)
add_excluded_domain(line)
if NO_PROXY_IPV6 == "1" then
set_domain_address(line, "::")
end
set_domain_dns(line, fwd_dns)
set_domain_ipset(line, table.concat(sets, ","))
end)
log(" - 解析[代理列表] Geosite 到代理域名表(blacklist)完成")
end
end
end
--GFW列表
if USE_GFW_LIST == "1" then
if fs.access("/usr/share/passwall/rules/gfwlist") then
fwd_dns = TUN_DNS
if USE_CHINADNS_NG == "1" then
fwd_dns = nil
fwd_dns = TUN_DNS
if USE_CHINADNS_NG == "1" then
fwd_dns = nil
end
if fwd_dns then
local set_name = "psw_gfw"
local set6_name = "psw_gfw6"
if FLAG ~= "default" then
set_name = "psw_" .. FLAG .. "_gfw"
set6_name = "psw_" .. FLAG .. "_gfw6"
end
if fwd_dns then
local set_name = "psw_gfw"
local set6_name = "psw_gfw6"
if FLAG ~= "default" then
set_name = "psw_" .. FLAG .. "_gfw"
set6_name = "psw_" .. FLAG .. "_gfw6"
end
local sets = {
setflag_4 .. set_name
}
if NO_PROXY_IPV6 ~= "1" then
table.insert(sets, setflag_6 .. set6_name)
end
for line in io.lines("/usr/share/passwall/rules/gfwlist") do
local sets = {
setflag_4 .. set_name
}
if NO_PROXY_IPV6 ~= "1" then
table.insert(sets, setflag_6 .. set6_name)
end
local f = io.open("/usr/share/passwall/rules/gfwlist")
if f then
for line in f:lines() do
if line ~= "" and not line:find("#") and not check_excluded_domain(line) then
if NO_PROXY_IPV6 == "1" then
set_domain_address(line, "::")
@@ -494,6 +562,7 @@ function add_rule(var)
set_domain_ipset(line, table.concat(sets, ","))
end
end
f:close()
log(string.format(" - 防火墙域名表(gfwlist)%s", fwd_dns or "默认"))
end
end
@@ -501,30 +570,31 @@ function add_rule(var)
--中国列表
if CHN_LIST ~= "0" then
if fs.access("/usr/share/passwall/rules/chnlist") then
fwd_dns = nil
if CHN_LIST == "direct" then
fwd_dns = LOCAL_DNS
end
if CHN_LIST == "proxy" then
fwd_dns = TUN_DNS
end
if USE_CHINADNS_NG == "1" then
fwd_dns = nil
if CHN_LIST == "direct" then
fwd_dns = LOCAL_DNS
end
end
if fwd_dns then
local sets = {
setflag_4 .. "psw_chn",
setflag_6 .. "psw_chn6"
}
if CHN_LIST == "proxy" then
fwd_dns = TUN_DNS
end
if USE_CHINADNS_NG == "1" then
fwd_dns = nil
end
if fwd_dns then
local sets = {
setflag_4 .. "psw_chn",
setflag_6 .. "psw_chn6"
}
if CHN_LIST == "proxy" then
if NO_PROXY_IPV6 == "1" then
sets = {
setflag_4 .. "psw_chn"
}
end
if NO_PROXY_IPV6 == "1" then
sets = {
setflag_4 .. "psw_chn"
}
end
for line in io.lines("/usr/share/passwall/rules/chnlist") do
end
local f = io.open("/usr/share/passwall/rules/chnlist")
if f then
for line in f:lines() do
if line ~= "" and not line:find("#") and not check_excluded_domain(line) then
if CHN_LIST == "proxy" and NO_PROXY_IPV6 == "1" then
set_domain_address(line, "::")
@@ -537,6 +607,7 @@ function add_rule(var)
set_domain_ipset(line, table.concat(sets, ","))
end
end
f:close()
log(string.format(" - 中国域名表(chnroute)%s", fwd_dns or "默认"))
end
end
@@ -590,21 +661,40 @@ function add_rule(var)
end
local domain_list = s.domain_list or ""
local geosite_arg = ""
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
if line ~= "" and not line:find("#") and not line:find("regexp:") and not line:find("ext:") and not line:find("rule-set:") and not line:find("rs:") then
if line:find("geosite:") then
line = string.match(line, ":([^:]+)$")
end
line = api.get_std_domain(line)
add_excluded_domain(line)
geosite_arg = geosite_arg .. (geosite_arg ~= "" and "," or "") .. line
else
if line:find("domain:") or line:find("full:") then
line = string.match(line, ":([^:]+)$")
end
line = api.get_std_domain(line)
add_excluded_domain(line)
if no_ipv6 then
set_domain_address(line, "::")
end
set_domain_dns(line, fwd_dns)
set_domain_ipset(line, table.concat(sets, ","))
end
end
end
if USE_GFW_LIST == "1" and CHN_LIST == "0" and USE_GEOVIEW == "1" and geosite_arg ~= "" then --仅GFW模式解析geosite
foreach_geosite(geosite_arg, function(line)
add_excluded_domain(line)
if no_ipv6 then
set_domain_address(line, "::")
end
set_domain_dns(line, fwd_dns)
set_domain_ipset(line, table.concat(sets, ","))
end
end)
log(string.format(" - 解析分流规则(%s) Geosite 完成", s.remarks))
end
if _node_id ~= "_direct" then
log(string.format(" - Sing-Box/Xray分流规则(%s)%s", s.remarks, fwd_dns or "默认"))
end
@@ -388,7 +388,7 @@ if USE_DIRECT_LIST == "1" and not fs.access(file_direct_host) then
geosite_arg = geosite_arg .. (geosite_arg ~= "" and "," or "") .. line
else
line = api.get_std_domain(line)
if line ~= "" and not line:find("#") then
if line ~= "" and not line:find("#") and not line:find(":") then
insert_unique(direct_domain, line, lookup_direct_domain)
end
end
@@ -440,7 +440,7 @@ if USE_PROXY_LIST == "1" and not fs.access(file_proxy_host) then
geosite_arg = geosite_arg .. (geosite_arg ~= "" and "," or "") .. line
else
line = api.get_std_domain(line)
if line ~= "" and not line:find("#") then
if line ~= "" and not line:find("#") and not line:find(":") then
insert_unique(proxy_domain, line, lookup_proxy_domain)
end
end
@@ -566,7 +566,7 @@ if IS_SHUNT_NODE then
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("ext:") then
if line ~= "" and not line:find("#") and not line:find("regexp:") and not line:find("ext:") and not line:find("rule-set:") and not line:find("rs:") then
if line:find("geosite:") then
line = string.match(line, ":([^:]+)$")
if _node_id == "_direct" then
+1 -1
View File
@@ -6,7 +6,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall2
PKG_VERSION:=26.7.16
PKG_VERSION:=26.8.7
PKG_RELEASE:=1
PKG_PO_VERSION:=$(PKG_VERSION)
@@ -0,0 +1,45 @@
function arraysEqual(a, b) {
if (a === b) return true;
if (a == null || b == null) return false;
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false;
}
return true;
}
function waitForElement(selector, callback) {
const el = document.querySelector(selector);
if (el) return callback(el);
const observer = new MutationObserver(() => {
const el = document.querySelector(selector);
if (el) {
observer.disconnect();
callback(el);
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
function get_current_url() {
return window.location.origin + window.location.pathname;
}
function getOption(config, section, opt) {
let obj;
const id = `cbid.${config}.${section}.${opt}`;
obj = document.getElementsByName(id)[0] || document.getElementById(id);
if (obj) {
const combobox = document.getElementById('cbi.combobox.' + id);
if (combobox) {
obj.combobox = combobox;
}
const div = document.getElementById(id);
if (div && div.getElementsByTagName("li").length > 0) {
obj = div;
}
return obj;
} else {
return null;
}
}
@@ -79,8 +79,8 @@ function index()
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, "update_config"}, call("update_config")).leaf = true
entry({"admin", "services", appname, "add_node"}, call("add_node")).leaf = true
entry({"admin", "services", appname, "update_node"}, call("update_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
@@ -96,6 +96,10 @@ function index()
entry({"admin", "services", appname, "subscribe_manual"}, call("subscribe_manual")).leaf = true
entry({"admin", "services", appname, "subscribe_manual_all"}, call("subscribe_manual_all")).leaf = true
entry({"admin", "services", appname, "flush_set"}, call("flush_set")).leaf = true
entry({"admin", "services", appname, "get_shunt_rules"}, call("get_shunt_rules")).leaf = true
entry({"admin", "services", appname, "add_shunt_rule"}, call("add_shunt_rule")).leaf = true
entry({"admin", "services", appname, "delete_select_shunt_rules"}, call("delete_select_shunt_rules")).leaf = true
entry({"admin", "services", appname, "save_shunt_rule_order"}, call("save_shunt_rule_order")).leaf = true
--[[Components update]]
entry({"admin", "services", appname, "check_passwall2"}, call("app_check")).leaf = true
@@ -369,6 +373,23 @@ function urltest_node()
http_write_json(e)
end
function update_config()
local id = http.formvalue("id") -- Node id
local data = http.formvalue("data") -- json new Data
if id and data then
local data_t = jsonParse(data) or {}
if next(data_t) then
for k, v in pairs(data_t) do
uci:set(appname, id, k, v)
end
api.uci_save(uci, appname)
http_write_json_ok()
return
end
end
http_write_json_error()
end
function add_node()
local redirect = http.formvalue("redirect")
@@ -391,23 +412,6 @@ function add_node()
end
end
function update_node()
local id = http.formvalue("id") -- Node id
local data = http.formvalue("data") -- json new Data
if id and data then
local data_t = jsonParse(data) or {}
if next(data_t) then
for k, v in pairs(data_t) do
uci:set(appname, id, k, v)
end
api.uci_save(uci, appname)
http_write_json_ok()
return
end
end
http_write_json_error()
end
function set_node()
local type = http.formvalue("type")
local config = http.formvalue("config")
@@ -968,3 +972,87 @@ function fetch_certsha256()
local data = api.fetch_cert_sha256(address, port, sni, 5)
http_write_json(data ~= "" and { code = 1, data = data } or { code = 0 })
end
function get_shunt_rules()
local id = http.formvalue("id")
local result = {}
if id then
result = uci:get_all(appname, id)
else
local default_items = {}
local other_items = {}
uci:foreach(appname, "shunt_rules", function(t)
if not t.group or t.group == "" then
default_items[#default_items + 1] = t
else
other_items[#other_items + 1] = t
end
end)
for i = 1, #default_items do result[#result + 1] = default_items[i] end
for i = 1, #other_items do result[#result + 1] = other_items[i] end
end
http_write_json(result)
end
function add_shunt_rule()
local add_name = http.formvalue("add_name")
local redirect = http.formvalue("redirect")
local uuid = add_name
if add_name then
local has = uci:get(appname, uuid)
if has then
http_write_json_error({ message = "This ID already exists." })
return
end
else
uuid = api.gen_short_uuid()
end
uci:section(appname, "shunt_rules", uuid)
local group = http.formvalue("group")
if group and group ~= "default" then
uci:set(appname, uuid, "group", group)
end
if redirect == "1" then
api.uci_save(uci, appname)
http.redirect(api.url("shunt_rules", uuid))
else
api.uci_save(uci, appname)
http_write_json_ok({uuid = uuid, redirect_url = api.url("shunt_rules", uuid)})
end
end
function delete_select_shunt_rules()
local ids = http.formvalue("ids")
local redirect = http.formvalue("redirect")
string.gsub(ids, '[^' .. "," .. ']+', function(w)
uci:foreach(appname, "nodes", function(s)
if s["protocol"] and s["protocol"] == "_shunt" then
uci:delete(appname, s[".name"], w)
end
end)
uci:delete(appname, w)
end)
if redirect == "1" then
api.uci_save(uci, appname)
http.redirect(api.url("rule"))
else
api.uci_save(uci, appname, true, true)
end
end
function save_shunt_rule_order()
local ids = http.formvalue("ids") or ""
local new_order = {}
for id in ids:gmatch("([^,]+)") do
new_order[#new_order + 1] = id
end
for idx, name in ipairs(new_order) do
luci.sys.call(string.format("uci -q reorder %s.%s=%d", appname, name, idx - 1))
end
api.sh_uci_commit(appname)
http_write_json({ status = "ok" })
end
@@ -101,6 +101,7 @@ if (has_singbox or has_xray) and #nodes_table > 0 then
if current_node.protocol == "_shunt" then
local shunt_lua = loadfile("/usr/lib/lua/luci/model/cbi/passwall2/client/include/shunt_options.lua")
setfenv(shunt_lua, getfenv(1))(m, s, {
s_cfgid = s:cfgsections()[1],
node_id = current_node_id,
node = current_node,
socks_list = socks_list,
@@ -4,9 +4,17 @@ if not data.node_id or not data.node then
return
end
local s_cfgid = data.s_cfgid
local current_node_id = data.node_id
local node_list = data.node_list or api.get_node_list()
local groups = {}
m.uci:foreach(appname, "shunt_rules", function(s)
if s.group and s.group ~= "" then
groups[s.group] = true
end
end)
local function get_cfgvalue()
return function(self, section)
return m:get(current_node_id, self.option)
@@ -106,15 +114,27 @@ if api.is_finded("geoview") then
.. "</ul>"
end
shunt_group = add_option(ListValue, "shunt_group", translate("Group Name"))
shunt_group:value("", translate("default"))
for k, v in pairs(groups) do
shunt_group:value(k)
end
local shunt_group_val = m:get(current_node_id, "shunt_group") or ""
shunt_group_val = shunt_group_val:lower()
local shunt_rules = {}
m.uci:foreach(appname, "shunt_rules", function(e)
e.id = e[".name"]
e.remarks = e.remarks or e[".name"]
e["_node_option"] = e[".name"]
e["_node_default"] = ""
e["_fakedns_option"] = e[".name"] .. "_fakedns"
e["_proxy_tag_option"] = e[".name"] .. "_proxy_tag"
table.insert(shunt_rules, e)
local group = e.group or ""
group = group:lower()
if group == shunt_group_val then
e.id = e[".name"]
e.remarks = e.remarks or e[".name"]
e["_node_option"] = e[".name"]
e["_node_default"] = ""
e["_fakedns_option"] = e[".name"] .. "_fakedns"
e["_proxy_tag_option"] = e[".name"] .. "_proxy_tag"
table.insert(shunt_rules, e)
end
end)
table.insert(shunt_rules, {
id = ".default",
@@ -199,6 +219,8 @@ end
local footer = Template(appname .. "/include/shunt_options")
footer.api = api
footer.config = m.config
footer.id = current_node_id
footer.s_cfgid = s_cfgid or current_node_id
footer.normal_list = api.jsonc.stringify(node_list.normal_list)
m:append(footer)
@@ -135,6 +135,9 @@ s.template = "cbi/tblsection"
s.extedit = api.url("node_subscribe_config", "%s")
function s.create(e, t)
local id = TypedSection.create(e, t)
uci:set(appname, id, "hysteria_up_mbps", "100")
uci:set(appname, id, "hysteria_down_mbps", "100")
api.uci_save(uci, appname)
luci.http.redirect(e.extedit:format(id))
end
@@ -223,11 +223,9 @@ if #hysteria2_type > 0 then
o = s:option(Value, "hysteria_up_mbps", "Hy/Hy2 " .. translate("Max upload Mbps"))
o.datatype = "uinteger"
o.default = "100"
o = s:option(Value, "hysteria_down_mbps", "Hy/Hy2 " .. translate("Max download Mbps"))
o.datatype = "uinteger"
o.default = "100"
end
o = s:option(Flag, "boot_update", translate("Update Once on Boot"), translate("Updates the subscription the first time runs automatically after each system boot."))
@@ -88,31 +88,6 @@ end
s:append(Template(appname .. "/rule/rule_version"))
local cfgname = "shunt_rules"
s = m:section(TypedSection, cfgname, "Sing-Box/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"))
local sortable = Template(appname .. "/cbi/sortable")
sortable.api = api
sortable.target_cfgname = cfgname
m:append(sortable)
s:append(Template(appname .. "/rule/shunt_rule_list"))
return api.return_map(m)
@@ -45,6 +45,19 @@ function clean_text(text)
:gsub("[ \t]*\n[ \t]*", "\n")
end
local remarks_lookup = {}
local groups = {}
m.uci:foreach(appname, "shunt_rules", function(s)
if s[".name"] ~= arg[1] then
if s.remarks then
remarks_lookup[s.remarks] = s[".name"]
end
if s.group and s.group ~= "" then
groups[s.group] = true
end
end
end)
s = m:section(NamedSection, arg[1], "shunt_rules", "")
s.addremove = false
s.dynamic = false
@@ -57,9 +70,34 @@ remarks.validate = function(self, value, section)
if value == "" then
return nil, translate("Remark cannot be empty.")
end
if remarks_lookup[value] then
return nil, translate("This remark already exists, please change a new remark.")
end
return value
end
o = s:option(Value, "group", translate("Group Name"))
o.default = ""
o:value("", translate("default"))
for k, v in pairs(groups) do
o:value(k)
end
o.write = function(self, section, value)
value = api.trim(value)
local lower = value:lower()
if lower == "" or lower == "default" then
return m:del(section, self.option)
end
for _, v in ipairs(self.keylist or {}) do
if v:lower() == lower then
return m:set(section, self.option, v)
end
end
m:set(section, self.option, value)
end
protocol = s:option(MultiValue, "protocol", translate("Protocol"))
protocol:value("http")
protocol:value("tls")
@@ -109,10 +109,12 @@ o.rewrite_option = o.option
o = s:option(Value, _n("recv_window_conn"), translate("QUIC connection receive window"))
o.rewrite_option = o.option
o = s:option(Value, _n("idle_timeout"), translate("Idle Timeout"), translate("Example:") .. "30s (4s~120s)")
o = s:option(Value, _n("idle_timeout"), translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)")
o.datatype = "range(4,120)"
o.rewrite_option = o.option
o = s:option(Value, _n("keep_alive_period"), translate("QUIC KeepAlive interval"), translate("Example:") .. "10s (2s~60s)")
o = s:option(Value, _n("keep_alive_period"), translate("QUIC KeepAlive interval"), translate("Units:seconds") .. " (2~60)")
o.datatype = "range(2,60)"
o.rewrite_option = o.option
o = s:option(Flag, _n("disable_mtu_discovery"), translate("Disable MTU detection"))
@@ -394,10 +394,12 @@ o:depends({ [_n("protocol")] = "hysteria2" })
o = s:option(Value, _n("hysteria2_down_mbps"), translate("Max download Mbps"))
o:depends({ [_n("protocol")] = "hysteria2" })
o = s:option(Value, _n("hysteria2_idle_timeout"), translate("Idle Timeout"), translate("Example:") .. "30s (4s~120s)")
o = s:option(Value, _n("hysteria2_idle_timeout"), translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)")
o.datatype = "range(4,120)"
o:depends({ [_n("protocol")] = "hysteria2"})
o = s:option(Value, _n("hysteria2_keep_alive_period"), translate("QUIC KeepAlive interval"), translate("Example:") .. "10s (2s~60s)")
o = s:option(Value, _n("hysteria2_keep_alive_period"), translate("QUIC KeepAlive interval"), translate("Units:seconds") .. " (2~60)")
o.datatype = "range(2,60)"
o:depends({ [_n("protocol")] = "hysteria2"})
o = s:option(Flag, _n("hysteria2_disable_mtu_discovery"), translate("Disable MTU detection"))
@@ -803,6 +805,9 @@ o:value("", translate("Auto"))
o:value("UseIPv4", translate("IPv4 Only"))
o:value("UseIPv6", translate("IPv6 Only"))
o = s:option(Flag, _n("happy_eyeballs"), translate("Enable Happy Eyeballs"), translate("Attempts IPv4 and IPv6 simultaneously; automatically uses the faster connection."))
o.default = 0
local protocols = s.fields[_n("protocol")].keylist
if #protocols > 0 then
for i, v in ipairs(protocols) do
@@ -814,7 +819,11 @@ if #protocols > 0 then
s.fields[_n("address")]:depends(depends_condition)
s.fields[_n("port")]:depends(depends_condition)
s.fields[_n("domain_resolver")]:depends(depends_condition)
s.fields[_n("domain_strategy")]:depends(depends_condition)
s.fields[_n("happy_eyeballs")]:depends(depends_condition)
local strategy_depends = api.clone(depends_condition)
strategy_depends[_n("happy_eyeballs")] = false
s.fields[_n("domain_strategy")]:depends(strategy_depends)
if v ~= "hysteria2" then
s.fields[_n("tcp_fast_open")]:depends({ [_n("protocol")] = v })
@@ -457,10 +457,12 @@ if singbox_tags:find("with_quic") then
o = s:option(Value, _n("hysteria2_down_mbps"), translate("Max download Mbps"))
o:depends({ [_n("protocol")] = "hysteria2" })
o = s:option(Value, _n("hysteria2_idle_timeout"), translate("Idle Timeout"), translate("Example:") .. "30s (4s~120s)")
o = s:option(Value, _n("hysteria2_idle_timeout"), translate("Idle Timeout"), translate("Units:seconds") .. " (4~120)")
o.datatype = "range(4,120)"
o:depends({ [_n("protocol")] = "hysteria2"})
o = s:option(Value, _n("hysteria2_keep_alive_period"), translate("QUIC KeepAlive interval"), translate("Example:") .. "10s (2s~60s)")
o = s:option(Value, _n("hysteria2_keep_alive_period"), translate("QUIC KeepAlive interval"), translate("Units:seconds") .. " (2~60)")
o.datatype = "range(2,60)"
o:depends({ [_n("protocol")] = "hysteria2"})
o = s:option(Flag, _n("hysteria2_disable_mtu_discovery"), translate("Disable MTU detection"))
@@ -629,6 +631,10 @@ if singbox_tags:find("with_utls") then
o:depends({ [_n("reality")] = true })
end
o = s:option(Flag, _n("anytls_disable_reuse"), translate("Disable TLS Reuse"))
o.default = 0
o:depends({ [_n("protocol")] = "anytls" })
o = s:option(ListValue, _n("transport"), translate("Transport"))
o:value("tcp", "TCP")
o:value("http", "HTTP")
+1 -3
View File
@@ -1170,7 +1170,7 @@ function to_extract(app_name, file, subfix)
exec("/bin/rm", {"-f", file})
return {
code = 1,
error = i18n.translate("Not installed %s, Can't unzip!" % { tools_name })
error = i18n.translatef("Not installed %s, Can't unzip!", tools_name)
}
end
end
@@ -1383,8 +1383,6 @@ function set_apply_on_parse(map)
if old then old(self) end
map:set("@global[0]", "timestamp", os.time())
end
local cbi = require "luci.cbi"
map:append(cbi.Template(appname .. "/cbi/optimize_cbi_ui"))
end
end
end
@@ -647,6 +647,8 @@ function gen_outbound(flag, node, tag, proxy_table)
idle_session_check_interval = "30s",
idle_session_timeout = "30s",
min_idle_session = 5,
disable_reuse = (node.anytls_disable_reuse == "1") and true or nil,
client_metadata = api.compare_versions(local_version, ">=", "1.13.16") and "anytls/0.0.13" or nil,
tls = tls
}
end
@@ -1599,6 +1601,9 @@ function gen_config(var)
--shunt rule
uci:foreach(appname, "shunt_rules", function(e)
if node["shunt_group"] ~= e.group then
return
end
local outboundTag = gen_shunt_node(e[".name"])
if outboundTag and e.remarks then
if outboundTag == "default" then
@@ -170,9 +170,15 @@ function gen_outbound(flag, node, tag, proxy_table)
mark = 255,
domainStrategy = node.domain_strategy or "UseIP",
tcpFastOpen = (node.tcp_fast_open == "1") and true or nil,
tcpMptcp = (node.tcpMptcp == "1") and true or nil
tcpMptcp = (node.tcpMptcp == "1") and true or nil,
happyEyeballs = (node.happy_eyeballs == "1") and {
TryDelayMs = 250,
PrioritizeIPv6 = false,
Interleave = 1,
MaxConcurrentTry = 4
} or nil
},
network = node.transport,
[(api.compare_versions(xray_version, "<", "26.7.11")) and "network" or "method"] = node.transport, -- Todo: Remove version check and "network"
security = node.stream_security,
tlsSettings = (node.stream_security == "tls") and {
serverName = node.tls_serverName,
@@ -659,7 +665,7 @@ function gen_config_server(node)
protocol = node.protocol,
settings = settings,
streamSettings = {
network = node.transport,
[(api.compare_versions(xray_version, "<", "26.7.11")) and "network" or "method"] = node.transport, -- Todo: Remove version check and "network"
security = "none",
tlsSettings = ("1" == node.tls) and {
disableSystemRoot = false,
@@ -1073,6 +1079,10 @@ function gen_config(var)
if outboundTag then
valid_nodes[#valid_nodes + 1] = outboundTag
end
-- Check if balancing node duplicates fallback node
if _node.fallback_node == blc_node_id then
_node.fallback_node = nil
end
end
end
if #valid_nodes == 0 then return nil end
@@ -1392,6 +1402,9 @@ function gen_config(var)
--shunt rule
uci:foreach(appname, "shunt_rules", function(e)
if node["shunt_group"] ~= e.group then
return
end
local outboundTag = gen_shunt_node(e[".name"])
if outboundTag and e.remarks then
if outboundTag == "default" then
@@ -1 +1,18 @@
<!-- map footer -->
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function () {
setTimeout(updateElements, 200);
function updateElements() {
// Remove extra line breaks between the value control and its description.
document.querySelectorAll("div.cbi-value-description").forEach(function(descDiv) {
var prev = descDiv.previousSibling;
while (prev && prev.nodeType === Node.TEXT_NODE && prev.textContent.trim() === "") {
prev = prev.previousSibling;
}
if (prev && prev.nodeType === Node.ELEMENT_NODE && prev.tagName === "BR") {
prev.remove();
}
});
}
});
</script>
@@ -1,2 +1,24 @@
<!-- map header -->
<script src="<%=resource%>/view/<%=self.api.appname%>/cbi.js"></script>
<script src="<%=resource%>/view/<%=self.api.appname%>/func.js"></script>
<script type="text/javascript">
//<![CDATA[
function update_config(cfgid, data, redirect_url) {
XHR.get('<%=self.api.url("update_config")%>', {
id: cfgid,
data: JSON.stringify(data)
},
function(x, data) {
if (x && x.status == 200 && data.code == 1) {
if (redirect_url) {
window.location.href = redirect_url;
}
}
else {
alert("<%:Error%>");
}
});
}
//]]>
</script>
@@ -1,22 +0,0 @@
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function () {
setTimeout(updateElements, 200);
function updateElements() {
document.querySelectorAll("input.btn.cbi-button.cbi-button-up").forEach(function(btn) {
btn.value = "<%:Move up%>";
});
document.querySelectorAll("input.btn.cbi-button.cbi-button-down").forEach(function(btn) {
btn.value = "<%:Move down%>";
});
document.querySelectorAll("div.cbi-value-description").forEach(function(descDiv) {
var prev = descDiv.previousSibling;
while (prev && prev.nodeType === Node.TEXT_NODE && prev.textContent.trim() === "") {
prev = prev.previousSibling;
}
if (prev && prev.nodeType === Node.ELEMENT_NODE && prev.tagName === "BR") {
prev.remove();
}
});
}
});
</script>
@@ -207,7 +207,7 @@ local appname = api.appname
let new_val = el.target.value
const new_hasItem = shunt_list.find(element => element.id == new_val);
if (new_hasItem) {
XHR.get('<%=api.url("update_node")%>', {
XHR.get('<%=api.url("update_config")%>', {
id: "<%=self.global_cfgid%>",
data: JSON.stringify({
node: new_val
@@ -53,6 +53,22 @@
}
}
}
document.addEventListener("DOMContentLoaded", function () {
waitForElement('select[name*="<%=self.config%>"][name*="shunt_group"]', function(el) {
let o_val = el.value;
el.addEventListener("change", () => {
el.blur();
if (o_val != el.value) {
let save = true;
if (save) {
update_config("<%=self.id%>", {
shunt_group: getOption("<%=self.config%>", "<%=self.s_cfgid%>", "shunt_group").value,
}, get_current_url());
}
}
});
});
});
document.addEventListener("DOMContentLoaded", () => setTimeout(() => {
refresh_depends();
const table_dom = document.getElementById("cbi-passwall2-shunt_option_list");
@@ -1,8 +1,8 @@
<%
local api = self.api
-%>
<script type="text/javascript">
//<![CDATA[
let node_id = "<%=self.section%>";
let node_config_url = '<%=self.api.url("node_config")%>/' + node_id;
document.addEventListener("DOMContentLoaded", function () {
waitForElement('select[name*="<%=self.config%>"][name*="type"]', function(el) {
let o_val = el.value;
@@ -11,13 +11,18 @@ local api = self.api
if (o_val != el.value) {
let save = true;
if (save) {
//document.getElementsByClassName('cbi-button-save')[0].click();
update_node({
remarks: getOption("remarks").value,
group: getOption("group").value,
type: getOption("type").value,
/*
const saveBtn = document.getElementsByClassName('cbi-button-save');
if (saveBtn && saveBtn.length > 0) {
saveBtn[saveBtn.length - 1].click();
}
*/
update_config(node_id, {
remarks: getOption("<%=self.config%>", "<%=self.section%>", "remarks").value,
group: getOption("<%=self.config%>", "<%=self.section%>", "group").value,
type: getOption("<%=self.config%>", "<%=self.section%>", "type").value,
protocol: ''
});
}, node_config_url);
}
}
});
@@ -31,12 +36,17 @@ local api = self.api
let name = name_split[name_split.length - 1];
let save = true;
if (save) {
//document.getElementsByClassName('cbi-button-save')[0].click();
update_node({
remarks: getOption("remarks").value,
group: getOption("group").value,
protocol: getOption(name).value
});
/*
const saveBtn = document.getElementsByClassName('cbi-button-save');
if (saveBtn && saveBtn.length > 0) {
saveBtn[saveBtn.length - 1].click();
}
*/
update_config(node_id, {
remarks: getOption("<%=self.config%>", "<%=self.section%>", "remarks").value,
group: getOption("<%=self.config%>", "<%=self.section%>", "group").value,
protocol: getOption("<%=self.config%>", "<%=self.section%>", name).value,
}, node_config_url);
} else {
window.location.href = node_config_url + "?select_proto=" + el.value;
}
@@ -1,64 +1,11 @@
<%
local api = self.api
-%>
<script type="text/javascript">
//<![CDATA[
let node_id = "<%=self.section%>";
let node_config_url = '<%=api.url("node_config")%>/' + node_id;
function waitForElement(selector, callback) {
const el = document.querySelector(selector);
if (el) return callback(el);
const observer = new MutationObserver(() => {
const el = document.querySelector(selector);
if (el) {
observer.disconnect();
callback(el);
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
function getOption(opt) {
var obj;
var id = 'cbid.<%=self.config%>.' + node_id + '.' + 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;
}
}
function update_node(data) {
XHR.get('<%=api.url("update_node")%>', {
id: node_id,
data: JSON.stringify(data)
},
function(x, data) {
if (x && x.status == 200 && data.code == 1) {
window.location.href = node_config_url;
}
else {
alert("<%:Error%>");
}
});
}
function fetchCertSha256(el) {
if (el.getAttribute("data-loading") === "1") return;
el.setAttribute("data-loading", "1");
el.style.pointerEvents = "none";
el.style.color = "red";
XHR.get('<%=api.url("fetch_certsha256")%>', { id: node_id }, function(x, res) {
XHR.get('<%=self.api.url("fetch_certsha256")%>', { id: "<%=self.section%>" }, function(x, res) {
el.setAttribute("data-loading", "0");
el.style.pointerEvents = "";
el.style.color = "";
@@ -1750,7 +1750,7 @@ local current_node = map:get(section)
opt.set(dom_prefix + 'tls', true);
opt.set(dom_prefix + 'reality', false);
opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default');
opt.set(dom_prefix + 'tls_serverName', queryParam.sni || '');
opt.set(dom_prefix + 'tls_serverName', queryParam.sni || queryParam.peer || '');
opt.set(dom_prefix + 'tls_allowInsecure', !((queryParam.allowinsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0'));
if (queryParam.fp && queryParam.fp.trim() != "") {
opt.set(dom_prefix + 'utls', true);
@@ -1761,7 +1761,7 @@ local current_node = map:get(section)
opt.set(dom_prefix + 'tls', true);
opt.set(dom_prefix + 'reality', true);
opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default');
opt.set(dom_prefix + 'tls_serverName', queryParam.sni || '');
opt.set(dom_prefix + 'tls_serverName', queryParam.sni || queryParam.peer || '');
if (queryParam.fp && queryParam.fp.trim() != "") {
opt.set(dom_prefix + 'utls', true);
opt.set(dom_prefix + 'fingerprint', queryParam.fp);
@@ -0,0 +1,475 @@
<%
local api = require "luci.passwall2.api"
local appname = api.appname
local uci = api.uci
-%>
<script src="<%=resource%>/view/<%=appname%>/Sortable.min.js"></script>
<style>
table th, .table .th {
text-align: center;
}
table td, .table .td {
text-align: center;
/* white-space: nowrap; */
word-break: keep-all;
}
.td.cbi-section-actions {
text-align: right !important;
}
.shunt_rule-wrapper {
align-items: center;
display: inline-flex !important;
gap: 4px;
}
.cbi-tabmenu > li {
margin-right: 2px !important;
}
.cbi-tabmenu > li:last-child {
margin-right: 0 !important;
}
.shunt_rule-wrapper .drag-handle {
cursor: grab !important;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-weight: 100;
padding: 0 !important;
line-height: inherit;
user-select: none;
align-self: stretch;
background-color: transparent;
}
.shunt_rule-wrapper .drag-handle:hover {
background: transparent;
}
/* enable flex for small screens*/
@media screen and (max-width: 1152px) {
.cbi-section-table-row {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
}
.cbi-section-table-titles {
display: none !important;
}
.td-actions {
padding-top: 0 !important;
border-top-width: 0 !important;
flex: 1 1 350px;
}
}
/* shrink actionbar even further for mobile devices */
@media screen and (max-width: 500px) {
.shunt_rule-wrapper {
gap: 0;
}
.cbi-button {
margin-left: 0 !important;
margin-right: 1px !important;
}
.td-actions {
padding-left: 5px!important;
padding-right: 5px !important;
}
}
.sortable-chosen {
background-color: rgba(220, 235, 245, 0.4) !important;
opacity: 0.7;
}
.sortable-ghost {
background: #cce5ff !important;
height: 3px !important;
}
.dragging-row {
background-color: rgba(131, 191, 255, 0.7) !important;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
</style>
<% if api.is_js_luci() then -%>
<script type="text/javascript">
var cbi_t = [];
function cbi_t_add(section, tab) {
var t = document.getElementById('tab.' + section + '.' + tab);
var c = document.getElementById('container.' + section + '.' + tab);
if( t && c ) {
cbi_t[section] = (cbi_t[section] || [ ]);
cbi_t[section][tab] = { 'tab': t, 'container': c, 'cid': c.id };
}
}
function cbi_t_switch(section, tab) {
if( cbi_t[section] && cbi_t[section][tab] ) {
// Before switching tabs, first deselect all currently active tabs.
var o = cbi_t[section][tab];
var h = document.getElementById('tab.' + section);
for( var tid in cbi_t[section] ) {
var o2 = cbi_t[section][tid];
if( o.tab.id != o2.tab.id ) {
o2.tab.className = o2.tab.className.replace(/(^| )cbi-tab( |$)/, " cbi-tab-disabled ");
o2.container.style.display = 'none';
}
else {
if(h) h.value = tab;
o2.tab.className = o2.tab.className.replace(/(^| )cbi-tab-disabled( |$)/, " cbi-tab ");
o2.container.style.display = 'block';
}
}
}
return false
}
</script>
<%- end %>
<script type="text/javascript">
//<![CDATA[
function toPage(action, cbi_id) {
if (action == "add") {
const dom = document.getElementsByClassName("cbi-tab")[0];
const current_group = dom ? (dom.getAttribute("group_name") || "default") : "default";
const add_name_val = document.getElementById("add_name_" + current_group).value;
if (add_name_val.length > 1) {
XHR.get('<%=api.url("add_shunt_rule")%>', {
add_name: add_name_val,
group: current_group
},
function(x, result) {
if (x && x.status === 200) {
if (result.code == 1) {
window.location.href = result.data.redirect_url;
} else {
alert(result.data.message);
}
} else {
alert("<%:Save failed!%>");
}
}
);
}
}
if (action == "edit") {
window.location.href='<%=api.url("shunt_rules")%>/' + cbi_id;
}
if (action == "del") {
window.location.href = '<%=api.url("delete_select_shunt_rules")%>' + "?redirect=1&ids=" + cbi_id;
}
}
function row_top(btn, group) {
const row = btn.closest("tr");
if (!row) return;
const parent = row.parentNode;
let firstDataRow = parent.querySelector("tr:not(.cbi-section-table-titles)");
if (firstDataRow && firstDataRow !== row) {
parent.insertBefore(row, firstDataRow);
let save_order_btn = document.getElementById("save_order_btn_" + group);
if (save_order_btn) {
const new_order = get_shunt_rule_order(group);
if (!arraysEqual(new_order, origin_group_shunt_rule_order[group])) {
save_order_btn.style.display = null;
} else {
save_order_btn.style.display = "none";
}
}
}
}
function get_shunt_rule_order(group) {
let table = document.getElementById("cbi-<%=appname%>-shunt_rules-" + group + "-table");
if (!table) {
return;
}
let rows = table.querySelectorAll("tr.cbi-section-table-row");
if (!rows || rows.length === 0) {
return;
}
var ids = [];
rows.forEach(function(row) {
var id = row.id.replace("cbi-<%=appname%>-", "");
ids.push(id);
});
return ids;
}
function save_current_page_order(group) {
var table = document.getElementById("cbi-<%=appname%>-shunt_rules-" + group + "-table");
if (!table) {
alert("<%:No table!%>");
return;
}
var rows = table.querySelectorAll("tr.cbi-section-table-row");
if (!rows || rows.length === 0) {
alert("<%:No shunt_rules!%>");
return;
}
var btn = document.getElementById("save_order_btn_" + group);
if (btn) {
btn.style.display = "none";
btn.disabled = true;
}
var ids = [];
rows.forEach(function(row) {
var id = row.id.replace("cbi-<%=appname%>-", "");
ids.push(id);
});
XHR.get('<%=api.url("save_shunt_rule_order")%>', {
group: group,
ids: ids.join(",")
},
function(x, result) {
if (btn) {
btn.style.display = null;
btn.disabled = false;
}
if (x && x.status === 200) {
origin_group_shunt_rule_order[group] = get_shunt_rule_order(group);
alert("<%:Saved current page order successfully.%>");
if (btn) {
btn.style.display = "none";
}
} else {
alert("<%:Save failed!%>");
}
}
);
}
function arraysEqual(a, b) {
if (a === b) return true;
if (a == null || b == null) return false;
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false;
}
return true;
}
// List drag and rearrange
function initSortableForTable(table) {
if (!table) return null;
let group = table.id.replace("cbi-<%=appname%>-shunt_rules-", "").replace("-table", "")
var root = table.querySelector('tbody') || table;
if (root._sortable_initialized) return root._sortable_instance;
root._sortable_initialized = true;
var opts = {
handle: ".drag-handle",
draggable: "tr.cbi-section-table-row",
animation: 150,
ghostClass: "dragging-row",
fallbackOnBody: true,
forceFallback: false,
swapThreshold: 0.65,
onEnd: function (evt) {
//save_current_page_order(group); // Auto save
let save_order_btn = document.getElementById("save_order_btn_" + group);
if (save_order_btn) {
const new_order = get_shunt_rule_order(group);
if (!arraysEqual(new_order, origin_group_shunt_rule_order[group])) {
save_order_btn.style.display = null;
} else {
save_order_btn.style.display = "none";
}
}
}
};
try {
var instance = Sortable.create(root, opts);
root._sortable_instance = instance;
return instance;
} catch (err) {
root._sortable_initialized = false;
console.error("Sortable init failed:", err);
return null;
}
}
function initAllSortable(group_shunt_rules) {
if (typeof Sortable === 'undefined') {
var retries = 0;
var maxRetries = 25;
var t = setInterval(function () {
retries++;
if (typeof Sortable !== 'undefined') {
clearInterval(t);
for (var group in group_shunt_rules) {
var table = document.getElementById("cbi-<%=appname%>-shunt_rules-" + group + "-table");
initSortableForTable(table);
}
} else if (retries >= maxRetries) {
clearInterval(t);
}
}, 200);
} else {
for (var group in group_shunt_rules) {
var table = document.getElementById("cbi-<%=appname%>-shunt_rules-" + group + "-table");
initSortableForTable(table);
}
}
}
function escape_html(s) {
return s.replace(/[&<>"']/g, c => ({
"&":"&amp;", "<":"&lt;", ">":"&gt;", '"':"&quot;", "'":"&#39;"
}[c]));
}
</script>
<script type="text/template" id="shunt_rules-table-template">
<fieldset class="cbi-section cbi-tblsection" id="cbi-<%=appname%>-shunt_rules-{{group}}-fieldset">
<table class="table cbi-section-table" id="cbi-<%=appname%>-shunt_rules-{{group}}-table" style="">
<tr class="tr cbi-section-table-titles anonymous">
<th class="th cbi-section-table-cell" style="width:30%">ID</th>
<th class="th cbi-section-table-cell" style="width:30%"><%:Remarks%></th>
<th class="th cbi-section-table-cell cbi-section-actions"></th>
</tr>
{{shunt_rule-tr}}
</table>
<div class="cbi-section-create cbi-tblsection-create">
<input type="text" id="add_name_{{group}}">
<input class="cbi-button cbi-button-add" type="button" value="<%:Add%>" onclick="toPage('add')">
<input class="cbi-button cbi-button-apply" style="display: none" type="button" id="save_order_btn_{{group}}" value="<%:Save Order%>" onclick="save_current_page_order('{{group}}')">
</div>
</fieldset>
</script>
<script type="text/template" id="shunt_rule-tr-template">
<tr class="tr cbi-section-table-row" id="cbi-<%=appname%>-{{id}}">
<td class="td cbi-value-field"><h3>{{id}}</h3></td>
<td class="td cbi-value-field">{{remarks}}</td>
<td class="td cbi-section-table-cell nowrap cbi-section-actions td-actions">
<div class="shunt_rule-wrapper">
<input class="btn cbi-button cbi-button-edit" type="button" value="<%:To Top%>" onclick="row_top(this, '{{group}}')" title="<%:To Top%>"/>
<input class="btn cbi-button cbi-button-edit" type="button" value="<%:Edit%>" onclick="toPage('edit', '{{id}}')" alt="<%:Edit%>" title="<%:Edit%>">
<input class="btn cbi-button cbi-button-remove" type="button" value="<%:Delete%>" onclick="toPage('del', '{{id}}')" alt="<%:Delete%>" title="<%:Delete%>">
<span class="drag-handle center" title="<%:Drag to reorder%>"></span>
</div>
</td>
</tr>
</script>
<fieldset class="cbi-section">
<legend>Sing-Box/Xray <%:Shunt Rule%></legend>
<div class="cbi-section-descr"><a style="color: red"><%:Please note attention to the priority, the higher the order, the higher the priority.%></a></div>
<div id="shunt_rule_list"></div>
</fieldset>
<script type="text/javascript">
function loadNodeList() {
XHR.get('<%=api.url("get_shunt_rules")%>', null, function(x, result) {
var shunt_rule_list = result
var group_shunt_rules = {}
for (let i = 0; i < shunt_rule_list.length; i++) {
let _shunt_rule = shunt_rule_list[i]
if (!_shunt_rule.group || _shunt_rule.group === "") {
_shunt_rule.group = "default"
}
if (!group_shunt_rules[_shunt_rule.group]) {
group_shunt_rules[_shunt_rule.group] = []
}
group_shunt_rules[_shunt_rule.group].push(_shunt_rule)
}
var tab_ul_li_html = '';
var tab_content_html = '';
var shunt_rules_table_template = document.getElementById("shunt_rules-table-template");
var shunt_rule_template = document.getElementById("shunt_rule-tr-template");
var default_group = null
for (let group in group_shunt_rules) {
if (default_group == null)
default_group = group
var table_html = "";
if (true) {
var new_shunt_rules_table_dom = shunt_rules_table_template.cloneNode(true);
var _html = new_shunt_rules_table_dom.innerHTML;
_html = _html.split("{{group}}").join(group);
var shunt_rule_tr_html = "";
for (var i = 0; i < group_shunt_rules[group].length; i++) {
let o = group_shunt_rules[group][i]
var newDom = shunt_rule_template.cloneNode(true);
newDom.classList.add("cbi-rowstyle-" + (i % 2 + 1));
var innerHTML = newDom.innerHTML;
innerHTML = innerHTML.split("{{id}}").join(o[".name"]);
innerHTML = innerHTML.split("{{group}}").join(o["group"] || "");
innerHTML = innerHTML.split("{{remarks}}").join(o["remarks"] || "");
shunt_rule_tr_html += innerHTML
}
_html = _html.split("{{shunt_rule-tr}}").join(shunt_rule_tr_html);
table_html = _html;
}
var group_name = group
if (group === "default") {
group_name = "<%:default%>"
}
tab_ul_li_html +=
'<li group_name="' + group + '" id="tab.<%=appname%>.shunt_rules.' + group + '" class="cbi-tab">' +
'<a onclick="this.blur(); return cbi_t_switch(\'<%=appname%>.shunt_rules\', \'' + group + '\')" href="<%=REQUEST_URI%>?tab.<%=appname%>.shunt_rules=' + group + '">' + escape_html(group_name) + " | " + "<font style='color: red'>" + group_shunt_rules[group].length + '</font></a>' +
'</li>'
tab_content_html +=
'<div class="cbi-tabcontainer" id="container.<%=appname%>.shunt_rules.' + group + '" style="display: none;">' +
'' + table_html +
'</div>'
}
function addTabDOM(li, content) {
let tab_ul_html = '<ul class="cbi-tabmenu">';
let tab_content_html = '<fieldset class="cbi-section-shunt_rule cbi-section-shunt_rule-tabbed" id="cbi-<%=appname%>-shunt_rules">';
if (li) {
tab_ul_html += li;
}
if (content) {
tab_content_html += content;
}
tab_ul_html += '</ul>';
tab_content_html += '</fieldset>';
return tab_ul_html + tab_content_html;
}
document.getElementById("shunt_rule_list").innerHTML = addTabDOM(tab_ul_li_html, tab_content_html);
for (let group in group_shunt_rules) {
cbi_t_add("<%=appname%>.shunt_rules", group)
}
if (default_group) {
cbi_t_switch("<%=appname%>.shunt_rules", default_group)
}
origin_group_shunt_rule_order = {};
for (let group in group_shunt_rules) {
origin_group_shunt_rule_order[group] = get_shunt_rule_order(group);
}
initAllSortable(group_shunt_rules);
});
}
loadNodeList();
//]]>
</script>
+12
View File
@@ -1750,6 +1750,12 @@ msgstr "حل مشکل DNS دامنه"
msgid "If the node address is a domain name, this DNS will be used for resolution."
msgstr "اگر آدرس گره یک نام دامنه باشد، از این DNS برای حل مسئله استفاده خواهد شد."
msgid "Enable Happy Eyeballs"
msgstr "فعال کردن چشم‌های خوشحال"
msgid "Attempts IPv4 and IPv6 simultaneously; automatically uses the faster connection."
msgstr "IPv4 و IPv6 را همزمان امتحان می‌کند؛ به‌طور خودکار از اتصال سریع‌تر استفاده می‌کند."
msgid "Supports only Xray or Sing-box node types."
msgstr "فقط گره‌های نوع Xray یا Sing-box پشتیبانی می‌شوند."
@@ -2413,3 +2419,9 @@ msgstr "اندازه بسته Gecko (حداکثر)"
msgid "valid time (hh:mm)"
msgstr "زمان معتبر (ساعت:میلی‌متر)"
msgid "Disable TLS Reuse"
msgstr "غیرفعال کردن استفاده مجدد از TLS"
msgid "Not Set"
msgstr "تنظیم نشده"
+12
View File
@@ -1751,6 +1751,12 @@ msgstr "Разрешение DNS-запросов для домена"
msgid "If the node address is a domain name, this DNS will be used for resolution."
msgstr "Если адрес узла является доменным именем, для разрешения будет использоваться этот DNS-сервер."
msgid "Enable Happy Eyeballs"
msgstr "Включить счастливые глаза"
msgid "Attempts IPv4 and IPv6 simultaneously; automatically uses the faster connection."
msgstr "Одновременно пытается использовать IPv4 и IPv6; автоматически выбирает более быстрое соединение."
msgid "Supports only Xray or Sing-box node types."
msgstr "Поддерживаются только узлы типа Xray или Sing-box."
@@ -2411,3 +2417,9 @@ msgstr "Размер упаковки Gecko (макс)"
msgid "valid time (hh:mm)"
msgstr "Время действия (чч:мм)"
msgid "Disable TLS Reuse"
msgstr "Отключить повторное использование TLS"
msgid "Not Set"
msgstr "Не задано"
+12
View File
@@ -1736,6 +1736,12 @@ msgstr "域名 DNS 解析"
msgid "If the node address is a domain name, this DNS will be used for resolution."
msgstr "如果节点地址是域名,则将使用此 DNS 进行解析。"
msgid "Enable Happy Eyeballs"
msgstr "启用 Happy Eyeballs"
msgid "Attempts IPv4 and IPv6 simultaneously; automatically uses the faster connection."
msgstr "同时尝试 IPv4 和 IPv6 连接,自动使用更快的连接。"
msgid "Supports only Xray or Sing-box node types."
msgstr "仅支持 Xray 或 Sing-box 类型节点。"
@@ -2399,3 +2405,9 @@ msgstr "Gecko 包大小(最大)"
msgid "valid time (hh:mm)"
msgstr "有效时间(hh:mm"
msgid "Disable TLS Reuse"
msgstr "禁用 TLS 连接复用"
msgid "Not Set"
msgstr "未设置"
+12
View File
@@ -1742,6 +1742,12 @@ msgstr "域名 DNS 解析"
msgid "If the node address is a domain name, this DNS will be used for resolution."
msgstr "如果節點地址是域名,則將使用此 DNS 進行解析。"
msgid "Enable Happy Eyeballs"
msgstr "啓用 Happy Eyeballs"
msgid "Attempts IPv4 and IPv6 simultaneously; automatically uses the faster connection."
msgstr "同時嘗試 IPv4 和 IPv6 鏈接,自動使用更快的鏈接。"
msgid "Supports only Xray or Sing-box node types."
msgstr "僅支援 Xray 或 Sing-box 類型節點。"
@@ -2405,3 +2411,9 @@ msgstr "Gecko 包大小(最大)"
msgid "valid time (hh:mm)"
msgstr "有效時間(hh:mm"
msgid "Disable TLS Reuse"
msgstr "禁用 TLS 連接復用"
msgid "Not Set"
msgstr "未設置"
@@ -59,51 +59,6 @@ config global_subscribe
config global_singbox
config shunt_rules 'China'
option remarks 'China'
option network 'tcp,udp'
option domain_list 'geosite:cn'
option ip_list 'geoip:cn'
config shunt_rules 'Iran'
option remarks 'Iran'
option network 'tcp,udp'
option domain_list 'geosite:ir
#rule-set:remote:https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-ir.srs'
option ip_list 'geoip:ir
#rule-set:remote:https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geoip-ir.srs'
config shunt_rules 'Russia_Block'
option remarks 'Russia_Block'
option network 'tcp,udp'
option domain_list 'geosite:ru-blocked
#rule-set:remote:https://raw.githubusercontent.com/runetfreedom/russia-v2ray-rules-dat/refs/heads/release/sing-box/rule-set-geosite/geosite-ru-blocked.srs'
option ip_list 'geoip:ru-blocked
#rule-set:remote:https://raw.githubusercontent.com/runetfreedom/russia-v2ray-rules-dat/refs/heads/release/sing-box/rule-set-geoip/geoip-ru-blocked.srs'
config shunt_rules 'Russia'
option remarks 'Russia'
option network 'tcp,udp'
option ip_list 'geoip:ru
#rule-set:remote:https://raw.githubusercontent.com/runetfreedom/russia-v2ray-rules-dat/refs/heads/release/sing-box/rule-set-geoip/geoip-ru.srs'
config shunt_rules 'PrivateIP'
option remarks 'PrivateIP'
option network 'tcp,udp'
option ip_list '10.0.0.0/8
127.0.0.0/8
198.51.100.0/24
192.0.0.0/24
169.254.0.0/16
224.0.0.0/3
192.168.0.0/16
192.88.99.0/24
100.64.0.0/10
172.16.0.0/12
0.0.0.0/8
192.0.2.0/24
203.0.113.0/24'
config nodes 'examplenode'
option remarks 'Example'
option type 'Xray'
@@ -126,3 +81,151 @@ config nodes 'rulenode'
option domainMatcher 'hybrid'
option write_ipset_direct '1'
option enable_geoview_ip '1'
option shunt_group 'CN'
config shunt_rules 'DirectFront'
option remarks 'DirectFront'
option network 'tcp,udp'
option domain_list 'domain:ip233.cn'
option group 'CN'
config shunt_rules 'ProxyFront'
option remarks 'ProxyFront'
option network 'tcp,udp'
option domain_list 'domain:www.gstatic.com
domain:ssl.gstatic.com
domain:fonts.gstatic.com'
option group 'CN'
config shunt_rules 'DirectGame'
option remarks 'DirectGame'
option domain_list '# steam直连域名获取国内CDN走国内线路下载
cm.steampowered.com
steamserver.net
# steam国内CDN华为云
steampipe.steamcontent.tnkjmec.com
# steam国内CDN白山云
st.dl.eccdnx.com
st.dl.bscstorage.net
st.dl.pinyuncloud.com
# steam国内CDN新流云(原金山云)(支持ipv6)
dl.steam.clngaa.com
# steam国内CDN网宿
cdn.mileweb.cs.steampowered.com.8686c.com
cdn-ws.content.steamchina.com
# steam国内CDN腾讯云 (蒸汽中国独占)
cdn-qc.content.steamchina.com
# steam国内CDN阿里云(支持ipv6)
cdn-ali.content.steamchina.com
xz.pphimalayanrt.com
lv.queniujq.cn
alibaba.cdn.steampipe.steamcontent.com
# 国内游戏geosite域名
geosite:category-games@cn'
option ip_list '# steam直连IP
45.121.184.0/24
103.10.124.0/23
103.28.54.0/24
146.66.152.0/24
146.66.155.0/24
153.254.86.0/24
155.133.224.0/22
155.133.230.0/24
155.133.232.0/23
155.133.234.0/24
155.133.236.0/22
155.133.240.0/23
155.133.244.0/23
155.133.246.0/24
155.133.248.0/21
162.254.192.0/21
185.25.182.0/23
190.217.32.0/22
192.69.96.0/22
205.196.6.0/24
208.64.200.0/22
208.78.164.0/22
205.185.194.0/24'
option network 'tcp,udp'
option group 'CN'
config shunt_rules 'ProxyGame'
option remarks 'ProxyGame'
option domain_list '# steam 商店/客服/聊天/网页布局/API/二维码/Google云同步 代理URL
steamcommunity.com
www.steamcommunity.com
store.steampowered.com
checkout.steampowered.com
api.steampowered.com
help.steampowered.com
login.steampowered.com
store.akamai.steamstatic.com
steambroadcast.akamaized.net
steamvideo-a.akamaihd.net
steamusercontent-a.akamaihd.net
steamstore-a.akamaihd.net
steamcommunity-a.akamaihd.net
steamcdn-a.akamaihd.net
steamuserimages-a.akamaihd.net
community.akamai.steamstatic.com
avatars.akamai.steamstatic.com
community.steamstatic.com
cdn.akamai.steamstatic.com
avatars.steamstatic.com
shared.akamai.steamstatic.com
clan.akamai.steamstatic.com
cdn.cloudflare.steamstatic.com
community.cloudflare.steamstatic.com
store.cloudflare.steamstatic.com
avatars.cloudflare.steamstatic.com
clan.cloudflare.steamstatic.com
shared.cloudflare.steamstatic.com
steam-chat.com
steamcloud-ugc.storage.googleapis.com
steamcloud-eu-ams.storage.googleapis.com
steamcloud-eu-fra.storage.googleapis.com
steamcloud-finland.storage.googleapis.com
steamcloud-saopaulo.storage.googleapis.com
steamcloud-singapore.storage.googleapis.com
steamcloud-sydney.storage.googleapis.com
steamcloud-taiwan.storage.googleapis.com
steamcloud-eu.storage.googleapis.com
domain:blizzard.com
geosite:category-games'
option network 'tcp,udp'
option group 'CN'
config shunt_rules 'CN'
option remarks 'CN'
option domain_list 'geosite:cn'
option ip_list 'geoip:cn'
option network 'tcp,udp'
option group 'CN'
config shunt_rules 'Iran'
option remarks 'Iran'
option network 'tcp,udp'
option domain_list 'geosite:ir
#rule-set:remote:https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geosite-ir.srs'
option ip_list 'geoip:ir
#rule-set:remote:https://raw.githubusercontent.com/Chocolate4U/Iran-sing-box-rules/rule-set/geoip-ir.srs'
option group 'IR'
config shunt_rules 'Russia_Block'
option remarks 'Russia_Block'
option network 'tcp,udp'
option domain_list 'geosite:ru-blocked
#rule-set:remote:https://raw.githubusercontent.com/runetfreedom/russia-v2ray-rules-dat/refs/heads/release/sing-box/rule-set-geosite/geosite-ru-blocked.srs'
option ip_list 'geoip:ru-blocked
#rule-set:remote:https://raw.githubusercontent.com/runetfreedom/russia-v2ray-rules-dat/refs/heads/release/sing-box/rule-set-geoip/geoip-ru-blocked.srs'
option group 'RU'
config shunt_rules 'Russia'
option remarks 'Russia'
option network 'tcp,udp'
option ip_list 'geoip:ru
#rule-set:remote:https://raw.githubusercontent.com/runetfreedom/russia-v2ray-rules-dat/refs/heads/release/sing-box/rule-set-geoip/geoip-ru.srs'
option group 'RU'
@@ -189,7 +189,9 @@ gen_shunt_list() {
local default_outbound="redirect"
[ "$default_node" = "_direct" ] && default_outbound="direct"
local shunt_ids=$(uci show $CONFIG | grep "=shunt_rules" | awk -F '.' '{print $2}' | awk -F '=' '{print $1}')
local shunt_group=$(config_n_get $node shunt_group)
for shunt_id in $shunt_ids; do
[ "${shunt_group}" != "$(config_n_get ${shunt_id} group)" ] && continue
local shunt_node=$(config_n_get ${node} "${shunt_id}")
[ -n "$shunt_node" ] && {
local ipset_v4="psw2_${node}_${shunt_id}"
@@ -702,15 +704,13 @@ add_firewall_rule() {
fi
$ipt_n -N PSW2
$ipt_n -A PSW2 $(dst $IPSET_DIRECT) -j RETURN
$ipt_n -A PSW2 $(dst $IPSET_VPS) -j RETURN
$ipt_n -A PSW2 $(comment "WAN_IP_RETURN") $(dst $IPSET_WAN) -j RETURN
[ "$accept_icmp" = "1" ] && insert_rule_after "$ipt_n" "PREROUTING" "prerouting_rule" "-p icmp -j PSW2"
[ -z "${is_tproxy}" ] && insert_rule_after "$ipt_n" "PREROUTING" "prerouting_rule" "-p tcp -j PSW2"
[ "$accept_icmp" = "1" ] && insert_rule_after "$ipt_n" "PREROUTING" "prerouting_rule" "$(dst $IPSET_DIRECT !) -p icmp -j PSW2"
[ -z "${is_tproxy}" ] && insert_rule_after "$ipt_n" "PREROUTING" "prerouting_rule" "$(dst $IPSET_DIRECT !) -p tcp -j PSW2"
$ipt_n -N PSW2_OUTPUT
$ipt_n -A PSW2_OUTPUT $(dst $IPSET_DIRECT) -j RETURN
$ipt_n -A PSW2_OUTPUT $(dst $IPSET_VPS) -j RETURN
$ipt_n -A PSW2_OUTPUT -m mark --mark 255 -j RETURN
@@ -722,10 +722,6 @@ add_firewall_rule() {
$ipt_n -I PREROUTING -m set --match-set $IPSET_DIRECT src -j PSW2_DNS
fi
$ipt_m -N PSW2_DIVERT
$ipt_m -A PSW2_DIVERT -j MARK --set-mark ${FWMARK}
$ipt_m -A PSW2_DIVERT -j ACCEPT
$ipt_m -N PSW2_RULE
$ipt_m -A PSW2_RULE -j CONNMARK --restore-mark
$ipt_m -A PSW2_RULE -m mark --mark ${FWMARK} -j RETURN
@@ -734,17 +730,16 @@ add_firewall_rule() {
$ipt_m -A PSW2_RULE -j CONNMARK --save-mark
$ipt_m -N PSW2
$ipt_m -A PSW2 $(dst $IPSET_DIRECT) -j RETURN
# Socket Only TCP, UDP Invalid.
$ipt_m -A PSW2 -p tcp -m socket -j MARK --set-mark ${FWMARK}
$ipt_m -A PSW2 -p tcp -m socket -j ACCEPT
$ipt_m -A PSW2 $(dst $IPSET_VPS) -j RETURN
$ipt_m -A PSW2 $(comment "WAN_IP_RETURN") $(dst $IPSET_WAN) -j RETURN
$ipt_m -A PSW2 -m conntrack --ctdir REPLY -j RETURN
insert_rule_before "$ipt_m" "PREROUTING" "mwan3" "-j PSW2"
# Only TCP, UDP Invalid.
insert_rule_before "$ipt_m" "PREROUTING" "PSW2" "-p tcp -m socket -j PSW2_DIVERT"
insert_rule_before "$ipt_m" "PREROUTING" "mwan3" "$(dst $IPSET_DIRECT !) -j PSW2"
$ipt_m -N PSW2_OUTPUT
$ipt_m -A PSW2_OUTPUT $(dst $IPSET_DIRECT) -j RETURN
$ipt_m -A PSW2_OUTPUT $(dst $IPSET_VPS) -j RETURN
$ipt_m -A PSW2_OUTPUT -m conntrack --ctdir REPLY -j RETURN
$ipt_m -A PSW2_OUTPUT -m mark --mark 255 -j RETURN
@@ -754,12 +749,10 @@ add_firewall_rule() {
[ "$accept_icmpv6" = "1" ] && {
$ip6t_n -N PSW2
$ip6t_n -A PSW2 $(dst $IPSET_DIRECT6) -j RETURN
$ip6t_n -A PSW2 $(dst $IPSET_VPS6) -j RETURN
$ip6t_n -A PREROUTING -p ipv6-icmp -j PSW2
$ip6t_n -A PREROUTING $(dst $IPSET_DIRECT6 !) -p ipv6-icmp -j PSW2
$ip6t_n -N PSW2_OUTPUT
$ip6t_n -A PSW2_OUTPUT $(dst $IPSET_DIRECT6) -j RETURN
$ip6t_n -A PSW2_OUTPUT $(dst $IPSET_VPS6) -j RETURN
$ip6t_n -A PSW2_OUTPUT -m mark --mark 255 -j RETURN
}
@@ -772,10 +765,6 @@ add_firewall_rule() {
$ip6t_n -I PREROUTING -m set --match-set $IPSET_DIRECT6 src -j PSW2_DNS
fi
$ip6t_m -N PSW2_DIVERT
$ip6t_m -A PSW2_DIVERT -j MARK --set-mark ${FWMARK}
$ip6t_m -A PSW2_DIVERT -j ACCEPT
$ip6t_m -N PSW2_RULE
$ip6t_m -A PSW2_RULE -j CONNMARK --restore-mark
$ip6t_m -A PSW2_RULE -m mark --mark ${FWMARK} -j RETURN
@@ -784,18 +773,17 @@ add_firewall_rule() {
$ip6t_m -A PSW2_RULE -j CONNMARK --save-mark
$ip6t_m -N PSW2
$ip6t_m -A PSW2 $(dst $IPSET_DIRECT6) -j RETURN
# Socket Only TCP, UDP Invalid.
$ip6t_m -A PSW2 -p tcp -m socket -j MARK --set-mark ${FWMARK}
$ip6t_m -A PSW2 -p tcp -m socket -j ACCEPT
$ip6t_m -A PSW2 $(dst $IPSET_VPS6) -j RETURN
$ip6t_m -A PSW2 $(comment "WAN6_IP_RETURN") $(dst $IPSET_WAN6) -j RETURN
$ip6t_m -A PSW2 -m conntrack --ctdir REPLY -j RETURN
insert_rule_before "$ip6t_m" "PREROUTING" "mwan3" "-j PSW2"
# Only TCP, UDP Invalid.
insert_rule_before "$ip6t_m" "PREROUTING" "PSW2" "-p tcp -m socket -j PSW2_DIVERT"
insert_rule_before "$ip6t_m" "PREROUTING" "mwan3" "$(dst $IPSET_DIRECT6 !) -j PSW2"
$ip6t_m -N PSW2_OUTPUT
$ip6t_m -A PSW2_OUTPUT -m mark --mark 255 -j RETURN
$ip6t_m -A PSW2_OUTPUT $(dst $IPSET_DIRECT6) -j RETURN
$ip6t_m -A PSW2_OUTPUT $(dst $IPSET_VPS6) -j RETURN
$ip6t_m -A PSW2_OUTPUT -m conntrack --ctdir REPLY -j RETURN
@@ -860,14 +848,14 @@ add_firewall_rule() {
# Loading local router proxy TCP
if [ -n "$NODE" ] && [ "$TCP_LOCALHOST_PROXY" = "1" ]; then
[ "$accept_icmp" = "1" ] && {
$ipt_n -A OUTPUT -p icmp -j PSW2_OUTPUT
$ipt_n -A OUTPUT $(dst $IPSET_DIRECT !) -p icmp -j PSW2_OUTPUT
$ipt_n -A PSW2_OUTPUT -p icmp -d $FAKE_IP $(REDIRECT)
add_shunt_t_rule "${SHUNT_LIST4}" "$ipt_n -A PSW2_OUTPUT -p icmp" "$(REDIRECT)"
$ipt_n -A PSW2_OUTPUT -p icmp $(REDIRECT)
}
[ "$accept_icmpv6" = "1" ] && {
$ip6t_n -A OUTPUT -p ipv6-icmp -j PSW2_OUTPUT
$ip6t_n -A OUTPUT $(dst $IPSET_DIRECT6 !) -p ipv6-icmp -j PSW2_OUTPUT
$ip6t_n -A PSW2_OUTPUT -p ipv6-icmp -d $FAKE_IP_6 $(REDIRECT)
add_shunt_t_rule "${SHUNT_LIST6}" "$ip6t_n -A PSW2_OUTPUT -p ipv6-icmp" "$(REDIRECT)"
$ip6t_n -A PSW2_OUTPUT -p ipv6-icmp $(REDIRECT)
@@ -882,11 +870,11 @@ add_firewall_rule() {
$ipt_tmp -A PSW2_OUTPUT -p tcp -d $FAKE_IP ${ipt_j}
add_shunt_t_rule "${SHUNT_LIST4}" "$ipt_tmp -A PSW2_OUTPUT -p tcp" "${ipt_j}" $TCP_REDIR_PORTS
add_port_rules "$ipt_tmp -A PSW2_OUTPUT -p tcp" $TCP_REDIR_PORTS "${ipt_j}"
[ -z "${is_tproxy}" ] && $ipt_n -A OUTPUT -p tcp -j PSW2_OUTPUT
[ -z "${is_tproxy}" ] && $ipt_n -A OUTPUT $(dst $IPSET_DIRECT !) -p tcp -j PSW2_OUTPUT
[ -n "${is_tproxy}" ] && {
$ipt_m -A PSW2 $(comment "${comment_l}") -p tcp -i lo $(REDIRECT $REDIR_PORT TPROXY)
$ipt_m -A PSW2 $(comment "${comment_l}") -p tcp -i lo -j RETURN
insert_rule_before "$ipt_m" "OUTPUT" "mwan3" "$(comment mangle-OUTPUT-PSW2) -p tcp -j PSW2_OUTPUT"
insert_rule_before "$ipt_m" "OUTPUT" "mwan3" "$(comment mangle-OUTPUT-PSW2) $(dst $IPSET_DIRECT !) -p tcp -j PSW2_OUTPUT"
}
[ "$PROXY_IPV6" == "1" ] && {
@@ -895,7 +883,7 @@ add_firewall_rule() {
add_port_rules "$ip6t_m -A PSW2_OUTPUT -p tcp" $TCP_REDIR_PORTS "-j PSW2_RULE"
$ip6t_m -A PSW2 $(comment "${comment_l}") -p tcp -i lo $(REDIRECT $REDIR_PORT TPROXY)
$ip6t_m -A PSW2 $(comment "${comment_l}") -p tcp -i lo -j RETURN
insert_rule_before "$ip6t_m" "OUTPUT" "mwan3" "$(comment mangle-OUTPUT-PSW2) -p tcp -j PSW2_OUTPUT"
insert_rule_before "$ip6t_m" "OUTPUT" "mwan3" "$(comment mangle-OUTPUT-PSW2) $(dst $IPSET_DIRECT6 !) -p tcp -j PSW2_OUTPUT"
}
[ -d "${TMP_IFACE_PATH}" ] && {
@@ -913,7 +901,7 @@ add_firewall_rule() {
add_port_rules "$ipt_m -A PSW2_OUTPUT -p udp" $UDP_REDIR_PORTS "-j PSW2_RULE"
$ipt_m -A PSW2 $(comment "${comment_l}") -p udp -i lo $(REDIRECT $REDIR_PORT TPROXY)
$ipt_m -A PSW2 $(comment "${comment_l}") -p udp -i lo -j RETURN
insert_rule_before "$ipt_m" "OUTPUT" "mwan3" "$(comment mangle-OUTPUT-PSW2) -p udp -j PSW2_OUTPUT"
insert_rule_before "$ipt_m" "OUTPUT" "mwan3" "$(comment mangle-OUTPUT-PSW2) $(dst $IPSET_DIRECT !) -p udp -j PSW2_OUTPUT"
[ "$PROXY_IPV6" == "1" ] && {
$ip6t_m -A PSW2_OUTPUT -p udp -d $FAKE_IP_6 -j PSW2_RULE
@@ -921,7 +909,7 @@ add_firewall_rule() {
add_port_rules "$ip6t_m -A PSW2_OUTPUT -p udp" $UDP_REDIR_PORTS "-j PSW2_RULE"
$ip6t_m -A PSW2 $(comment "${comment_l}") -p udp -i lo $(REDIRECT $REDIR_PORT TPROXY)
$ip6t_m -A PSW2 $(comment "${comment_l}") -p udp -i lo -j RETURN
insert_rule_before "$ip6t_m" "OUTPUT" "mwan3" "$(comment mangle-OUTPUT-PSW2) -p udp -j PSW2_OUTPUT"
insert_rule_before "$ip6t_m" "OUTPUT" "mwan3" "$(comment mangle-OUTPUT-PSW2) $(dst $IPSET_DIRECT6 !) -p udp -j PSW2_OUTPUT"
}
[ -d "${TMP_IFACE_PATH}" ] && {
@@ -938,8 +926,10 @@ add_firewall_rule() {
$ip6t_m -I OUTPUT $(comment "mangle-OUTPUT-PSW2") -o lo -j RETURN
insert_rule_before "$ip6t_m" "OUTPUT" "mwan3" "$(comment mangle-OUTPUT-PSW2) -m mark --mark ${FWMARK} -j RETURN"
$ipt_m -A PSW2 -p udp --dport 53 -j RETURN
$ip6t_m -A PSW2 -p udp --dport 53 -j RETURN
$ipt_m -A PSW2 -p tcp --dport 53 -j ACCEPT
$ipt_m -A PSW2 -p udp --dport 53 -j ACCEPT
$ip6t_m -A PSW2 -p tcp --dport 53 -j ACCEPT
$ip6t_m -A PSW2 -p udp --dport 53 -j ACCEPT
}
load_acl
@@ -970,7 +960,7 @@ del_firewall_rule() {
$ipt -D $chain $index 2>/dev/null
done
done
for chain in "PSW2" "PSW2_OUTPUT" "PSW2_DIVERT" "PSW2_DNS" "PSW2_RULE"; do
for chain in "PSW2" "PSW2_OUTPUT" "PSW2_DNS" "PSW2_RULE"; do
$ipt -F $chain 2>/dev/null
$ipt -X $chain 2>/dev/null
done
@@ -1004,7 +994,7 @@ gen_include() {
[ -z "${_ipt}" ] && return
echo "*$2"
${_ipt}-save -t $2 | grep "PSW2" | grep -v "\-j PSW2$" | grep -v "socket \-j PSW2_DIVERT$" | sed -e "s/^-A \(OUTPUT\|PREROUTING\)/-I \1 1/"
${_ipt}-save -t $2 | grep "PSW2" | grep -v "\-j PSW2$" | sed -e "s/^-A \(OUTPUT\|PREROUTING\)/-I \1 1/"
echo 'COMMIT'
}
local __ipt=""
@@ -1017,11 +1007,10 @@ gen_include() {
$(extract_rules 4 mangle)
EOT
[ "$accept_icmp" = "1" ] && \$(${MY_PATH} insert_rule_after "$ipt_n" "PREROUTING" "prerouting_rule" "-p icmp -j PSW2")
[ -z "${is_tproxy}" ] && \$(${MY_PATH} insert_rule_after "$ipt_n" "PREROUTING" "prerouting_rule" "-p tcp -j PSW2")
[ "$accept_icmp" = "1" ] && \$(${MY_PATH} insert_rule_after "$ipt_n" "PREROUTING" "prerouting_rule" "$(dst $IPSET_DIRECT !) -p icmp -j PSW2")
[ -z "${is_tproxy}" ] && \$(${MY_PATH} insert_rule_after "$ipt_n" "PREROUTING" "prerouting_rule" "$(dst $IPSET_DIRECT !) -p tcp -j PSW2")
\$(${MY_PATH} insert_rule_before "$ipt_m" "PREROUTING" "mwan3" "-j PSW2")
\$(${MY_PATH} insert_rule_before "$ipt_m" "PREROUTING" "PSW2" "-p tcp -m socket -j PSW2_DIVERT")
\$(${MY_PATH} insert_rule_before "$ipt_m" "PREROUTING" "mwan3" "$(dst $IPSET_DIRECT !) -j PSW2")
EOF
)
}
@@ -1035,10 +1024,9 @@ gen_include() {
$(extract_rules 6 mangle)
EOT
[ "$accept_icmpv6" = "1" ] && $ip6t_n -A PREROUTING -p ipv6-icmp -j PSW2
[ "$accept_icmpv6" = "1" ] && $ip6t_n -A PREROUTING $(dst $IPSET_DIRECT6 !) -p ipv6-icmp -j PSW2
\$(${MY_PATH} insert_rule_before "$ip6t_m" "PREROUTING" "mwan3" "-j PSW2")
\$(${MY_PATH} insert_rule_before "$ip6t_m" "PREROUTING" "PSW2" "-p tcp -m socket -j PSW2_DIVERT")
\$(${MY_PATH} insert_rule_before "$ip6t_m" "PREROUTING" "mwan3" "$(dst $IPSET_DIRECT6 !) -j PSW2")
EOF
)
}
@@ -231,7 +231,9 @@ gen_shunt_list() {
local default_outbound="redirect"
[ "$default_node" = "_direct" ] && default_outbound="direct"
local shunt_ids=$(uci show $CONFIG | grep "=shunt_rules" | awk -F '.' '{print $2}' | awk -F '=' '{print $1}')
local shunt_group=$(config_n_get $node shunt_group)
for shunt_id in $shunt_ids; do
[ "${shunt_group}" != "$(config_n_get ${shunt_id} group)" ] && continue
local shunt_node=$(config_n_get ${node} "${shunt_id}")
[ -n "$shunt_node" ] && {
local nftset_v4="psw2_${node}_${shunt_id}"
@@ -700,13 +702,13 @@ add_firewall_rule() {
for ip in $(cat /usr/share/passwall2/direct_ip | tr -s "\r\n" "\n" | grep -v "^#" | sed -e "/^$/d"); do
if [[ "$ip" == *::* ]]; then
echo "$ip" | insert_nftset $NFTSET_DIRECT6 0
echo "$ip" | insert_nftset $NFTSET_DIRECT6 "-1"
elif [[ "$ip" == "geoip:"* ]]; then
local _geoip_code=$(echo $ip | awk -F ':' '{print $2}')
get_geoip $_geoip_code ipv4 | grep -E "(\.((2(5[0-5]|[0-4][0-9]))|[0-1]?[0-9]{1,2})){3}" | insert_nftset $NFTSET_DIRECT "0"
get_geoip $_geoip_code ipv6 | grep -E "([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4}" | insert_nftset $NFTSET_DIRECT6 "0"
get_geoip $_geoip_code ipv4 | grep -E "(\.((2(5[0-5]|[0-4][0-9]))|[0-1]?[0-9]{1,2})){3}" | insert_nftset $NFTSET_DIRECT "-1"
get_geoip $_geoip_code ipv6 | grep -E "([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4}" | insert_nftset $NFTSET_DIRECT6 "-1"
else
echo "$ip" | insert_nftset $NFTSET_DIRECT 0
echo "$ip" | insert_nftset $NFTSET_DIRECT "-1"
fi
done
@@ -726,14 +728,14 @@ add_firewall_rule() {
update_wan_sets
[ -n "$ISP_DNS" ] && {
echo "$ISP_DNS" | insert_nftset $NFTSET_DIRECT 0
echo "$ISP_DNS" | insert_nftset $NFTSET_DIRECT "-1"
for ispip in $ISP_DNS; do
log_i18n 1 "$(i18n "Add ISP %s DNS to the whitelist: %s" "IPv4" "${ispip}")"
done
}
[ -n "$ISP_DNS6" ] && {
echo $ISP_DNS6 | insert_nftset $NFTSET_DIRECT6 0
echo $ISP_DNS6 | insert_nftset $NFTSET_DIRECT6 "-1"
for ispip6 in $ISP_DNS6; do
log_i18n 1 "$(i18n "Add ISP %s DNS to the whitelist: %s" "IPv6" "${ispip6}")"
done
@@ -762,11 +764,6 @@ add_firewall_rule() {
nft_output_chain="PSW2_OUTPUT_MANGLE"
fi
nft "add chain $NFTABLE_NAME PSW2_DIVERT"
nft "flush chain $NFTABLE_NAME PSW2_DIVERT"
# Only TCP, UDP Invalid.
nft "add rule $NFTABLE_NAME PSW2_DIVERT meta l4proto tcp socket transparent 1 mark set ${FWMARK} counter accept"
nft "add chain $NFTABLE_NAME PSW2_DNS"
nft "flush chain $NFTABLE_NAME PSW2_DNS"
if [ $(config_t_get global dns_redirect "1") = "0" ]; then
@@ -790,33 +787,29 @@ add_firewall_rule() {
#ipv4 tproxy mode and udp
nft "add chain $NFTABLE_NAME PSW2_MANGLE"
nft "flush chain $NFTABLE_NAME PSW2_MANGLE"
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip daddr @$NFTSET_DIRECT counter return"
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip daddr @$NFTSET_VPS counter return"
nft "add rule $NFTABLE_NAME PSW2_MANGLE ct direction reply counter return"
nft "add chain $NFTABLE_NAME PSW2_OUTPUT_MANGLE"
nft "flush chain $NFTABLE_NAME PSW2_OUTPUT_MANGLE"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE ip daddr @$NFTSET_DIRECT counter return"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE ip daddr @$NFTSET_VPS counter return"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE ct direction reply counter return"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE meta mark 255 counter return"
# jump chains
nft "add rule $NFTABLE_NAME mangle_prerouting counter jump PSW2_DIVERT"
nft "add rule $NFTABLE_NAME mangle_prerouting ip protocol udp counter jump PSW2_MANGLE"
[ -n "${is_tproxy}" ] && nft "add rule $NFTABLE_NAME mangle_prerouting ip protocol tcp counter jump PSW2_MANGLE"
# Only TCP, UDP Invalid.
nft "add rule $NFTABLE_NAME mangle_prerouting meta nfproto ipv4 meta l4proto tcp socket transparent 1 mark set ${FWMARK} counter accept"
nft "add rule $NFTABLE_NAME mangle_prerouting ip daddr != @$NFTSET_DIRECT ip protocol udp counter jump PSW2_MANGLE"
[ -n "${is_tproxy}" ] && nft "add rule $NFTABLE_NAME mangle_prerouting ip daddr != @$NFTSET_DIRECT ip protocol tcp counter jump PSW2_MANGLE"
#ipv4 tcp redirect mode
[ -z "${is_tproxy}" ] && {
nft "add chain $NFTABLE_NAME PSW2_NAT"
nft "flush chain $NFTABLE_NAME PSW2_NAT"
nft "add rule $NFTABLE_NAME PSW2_NAT ip daddr @$NFTSET_DIRECT counter return"
nft "add rule $NFTABLE_NAME PSW2_NAT ip daddr @$NFTSET_VPS counter return"
nft "add rule $NFTABLE_NAME dstnat ip protocol tcp counter jump PSW2_NAT"
nft "add rule $NFTABLE_NAME dstnat ip daddr != @$NFTSET_DIRECT ip protocol tcp counter jump PSW2_NAT"
nft "add chain $NFTABLE_NAME PSW2_OUTPUT_NAT"
nft "flush chain $NFTABLE_NAME PSW2_OUTPUT_NAT"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_NAT ip daddr @$NFTSET_DIRECT counter return"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_NAT ip daddr @$NFTSET_VPS counter return"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_NAT meta mark 255 counter return"
}
@@ -825,16 +818,16 @@ add_firewall_rule() {
if [ "$accept_icmp" = "1" ]; then
nft "add chain $NFTABLE_NAME PSW2_ICMP_REDIRECT"
nft "flush chain $NFTABLE_NAME PSW2_ICMP_REDIRECT"
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip daddr @$NFTSET_DIRECT counter return"
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip daddr @$NFTSET_VPS counter return"
[ "$accept_icmpv6" = "1" ] && {
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip6 daddr @$NFTSET_DIRECT6 counter return"
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip6 daddr @$NFTSET_VPS6 counter return"
}
nft "add rule $NFTABLE_NAME dstnat meta l4proto {icmp,icmpv6} counter jump PSW2_ICMP_REDIRECT"
nft "add rule $NFTABLE_NAME nat_output meta l4proto {icmp,icmpv6} counter jump PSW2_ICMP_REDIRECT"
nft "add rule $NFTABLE_NAME dstnat ip daddr != @$NFTSET_DIRECT meta l4proto icmp counter jump PSW2_ICMP_REDIRECT"
nft "add rule $NFTABLE_NAME dstnat ip6 daddr != @$NFTSET_DIRECT6 meta l4proto icmpv6 counter jump PSW2_ICMP_REDIRECT"
nft "add rule $NFTABLE_NAME nat_output ip daddr != @$NFTSET_DIRECT meta l4proto icmp counter jump PSW2_ICMP_REDIRECT"
nft "add rule $NFTABLE_NAME nat_output ip6 daddr != @$NFTSET_DIRECT6 meta l4proto icmpv6 counter jump PSW2_ICMP_REDIRECT"
fi
#ipv4 wan_ip
@@ -847,13 +840,11 @@ add_firewall_rule() {
#ipv6 tproxy mode and udp
nft "add chain $NFTABLE_NAME PSW2_MANGLE_V6"
nft "flush chain $NFTABLE_NAME PSW2_MANGLE_V6"
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 ip6 daddr @$NFTSET_DIRECT6 counter return"
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 ip6 daddr @$NFTSET_VPS6 counter return"
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 ct direction reply counter return"
nft "add chain $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6"
nft "flush chain $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 ip6 daddr @$NFTSET_DIRECT6 counter return"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 ip6 daddr @$NFTSET_VPS6 counter return"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 ct direction reply counter return"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta mark 255 counter return"
@@ -874,8 +865,10 @@ add_firewall_rule() {
# jump chains
[ "$PROXY_IPV6" == "1" ] && {
nft "add rule $NFTABLE_NAME mangle_prerouting meta nfproto {ipv6} counter jump PSW2_MANGLE_V6"
nft "add rule $NFTABLE_NAME mangle_output meta nfproto {ipv6} counter jump PSW2_OUTPUT_MANGLE_V6 comment \"PSW2_OUTPUT_MANGLE\""
# Only TCP, UDP Invalid.
nft "add rule $NFTABLE_NAME mangle_prerouting meta nfproto ipv6 meta l4proto tcp socket transparent 1 mark set ${FWMARK} counter accept"
nft "add rule $NFTABLE_NAME mangle_prerouting ip6 daddr != @$NFTSET_DIRECT6 meta nfproto {ipv6} counter jump PSW2_MANGLE_V6"
nft "add rule $NFTABLE_NAME mangle_output ip6 daddr != @$NFTSET_DIRECT6 meta nfproto {ipv6} counter jump PSW2_OUTPUT_MANGLE_V6 comment \"PSW2_OUTPUT_MANGLE\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 ip6 daddr @$NFTSET_WAN6 counter return comment \"WAN6_IP_RETURN\""
@@ -948,11 +941,11 @@ add_firewall_rule() {
nft "add rule $NFTABLE_NAME $nft_chain ip protocol tcp ip daddr $FAKE_IP ${nft_j}"
add_shunt_t_rule "${SHUNT_LIST4}" "nft add rule $NFTABLE_NAME $nft_chain ip protocol tcp $(factor $TCP_REDIR_PORTS "tcp dport") ip daddr" "${nft_j}"
nft "add rule $NFTABLE_NAME $nft_chain ip protocol tcp $(factor $TCP_REDIR_PORTS "tcp dport") ${nft_j}"
[ -z "${is_tproxy}" ] && nft "add rule $NFTABLE_NAME nat_output ip protocol tcp counter jump PSW2_OUTPUT_NAT"
[ -z "${is_tproxy}" ] && nft "add rule $NFTABLE_NAME nat_output ip daddr != @$NFTSET_DIRECT ip protocol tcp counter jump PSW2_OUTPUT_NAT"
[ -n "${is_tproxy}" ] && {
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol tcp iif lo $(REDIRECT $REDIR_PORT TPROXY4) comment \"${comment_l}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol tcp iif lo counter return comment \"${comment_l}\""
nft "add rule $NFTABLE_NAME mangle_output ip protocol tcp counter jump PSW2_OUTPUT_MANGLE comment \"PSW2_OUTPUT_MANGLE\""
nft "add rule $NFTABLE_NAME mangle_output ip daddr != @$NFTSET_DIRECT ip protocol tcp counter jump PSW2_OUTPUT_MANGLE comment \"PSW2_OUTPUT_MANGLE\""
}
[ "$PROXY_IPV6" == "1" ] && {
@@ -978,7 +971,7 @@ add_firewall_rule() {
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE ip protocol udp $(factor $UDP_REDIR_PORTS "udp dport") counter jump PSW2_RULE"
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp iif lo $(REDIRECT $REDIR_PORT TPROXY4) comment \"${comment_l}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp iif lo counter return comment \"${comment_l}\""
nft "add rule $NFTABLE_NAME mangle_output ip protocol udp counter jump PSW2_OUTPUT_MANGLE comment \"PSW2_OUTPUT_MANGLE\""
nft "add rule $NFTABLE_NAME mangle_output ip daddr != @$NFTSET_DIRECT ip protocol udp counter jump PSW2_OUTPUT_MANGLE comment \"PSW2_OUTPUT_MANGLE\""
[ "$PROXY_IPV6" == "1" ] && {
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto udp ip6 daddr $FAKE_IP_6 jump PSW2_RULE"
@@ -996,11 +989,13 @@ add_firewall_rule() {
}
fi
nft "add rule $NFTABLE_NAME mangle_output oif lo counter return comment \"PSW2_OUTPUT_MANGLE\""
nft "add rule $NFTABLE_NAME mangle_output meta mark ${FWMARK} counter return comment \"PSW2_OUTPUT_MANGLE\""
nft "add rule $NFTABLE_NAME mangle_output ip daddr != @$NFTSET_DIRECT oif lo counter return comment \"PSW2_OUTPUT_MANGLE\""
nft "add rule $NFTABLE_NAME mangle_output ip daddr != @$NFTSET_DIRECT meta mark ${FWMARK} counter return comment \"PSW2_OUTPUT_MANGLE\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp udp dport 53 counter return"
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp udp dport 53 counter return"
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol tcp tcp dport 53 counter accept"
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp udp dport 53 counter accept"
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp tcp dport 53 counter accept"
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp udp dport 53 counter accept"
}
load_acl
@@ -126,7 +126,11 @@ test_auto_switch() {
# If the current node is not found, or if the current node is the last node, then take the first node.
[ -z "$new_node" ] && new_node="$first_node"
local msg2="$(i18n "next backup node")"
[ "$now_node" = "$main_node" ] && msg2="$(i18n "backup node")"
if [ "$new_node" = "$main_node" ]; then
msg2="$(i18n "main node")"
else
[ "$now_node" = "$main_node" ] && msg2="$(i18n "backup node")"
fi
msg="$(i18n "switch to %s test detect!" "${msg2}")"
else
# When there is only one backup node, poll with the primary node.
@@ -138,11 +142,6 @@ test_auto_switch() {
log_i18n 0 "Socks switch detection: %s 【%s:[%s]】 abnormal, %s" "${id}" "$(config_n_get $now_node type)" "$(config_n_get $now_node remarks)" "${msg}"
test_node ${new_node}
if [ $? -eq 0 ]; then
# [ "$restore_switch" = "0" ] && {
# uci set $CONFIG.${id}.node=$new_node
# [ -z "$(echo $b_nodes | grep $main_node)" ] && uci add_list $CONFIG.${id}.autoswitch_backup_node=$main_node
# uci commit $CONFIG
# }
check_process
log_i18n 0 "Socks switch detection: %s 【%s:[%s]】 normal, switch to this node!" "${id}" "$(config_n_get $new_node type)" "$(config_n_get $new_node remarks)"
$APP_FILE socks_node_switch flag=${id} new_node=${new_node}
@@ -171,6 +170,10 @@ start() {
backup_node_num=$(printf "%s\n" "$backup_node" | wc -w)
if [ "$backup_node_num" -eq 1 ]; then
[ "$main_node" = "$backup_node" ] && return
elif [ "$backup_node_num" -gt 1 ]; then
[ "$restore_switch" != "1" ] && {
[ -z "$(echo $backup_node | grep -F "$main_node")" ] && backup_node="${backup_node} ${main_node}"
}
fi
else
return
@@ -768,6 +768,9 @@ local function parseClashNode(node, add_mode, group, sub_cfg)
result.reality_publicKey = node["reality-opts"]["public-key"]
result.reality_shortId = node["reality-opts"]["short-id"]
end
if node["disable-reuse"] then
result.anytls_disable_reuse = "1"
end
end
if not result.remarks or result.remarks == "" then
if result.address and result.port then
@@ -1865,7 +1868,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
end
if params.security == "tls" or params.security == "reality" then
result.tls = "1"
result.tls_serverName = params.sni
result.tls_serverName = params.sni or params.peer
result.alpn = params.alpn
if params.fp and params.fp ~= "" then
result.utls = "1"