🐤 Sync 2025-11-15 00:12:24

This commit is contained in:
actions-user
2025-11-15 00:12:24 +08:00
parent 001b5f14c3
commit 7d627be079
51 changed files with 3355 additions and 616 deletions

View File

@@ -10,7 +10,7 @@ LUCI_DEPENDS:=+luci-base +luci-lib-jsonc +curl +bandix
PKG_MAINTAINER:=timsaya PKG_MAINTAINER:=timsaya
PKG_VERSION:=0.8.0 PKG_VERSION:=0.8.1
PKG_RELEASE:=1 PKG_RELEASE:=1
include $(TOPDIR)/feeds/luci/luci.mk include $(TOPDIR)/feeds/luci/luci.mk

View File

@@ -570,11 +570,11 @@ function formatDnsServer(query) {
function formatResponseResult(query) { function formatResponseResult(query) {
if (!query) return { display: [], full: [] }; if (!query) return { display: [], full: [] };
// 显示响应IPresponse_ips它是一个字符串数组 // 显示响应记录response_records它是一个字符串数组
if (query.response_ips && Array.isArray(query.response_ips) && query.response_ips.length > 0) { if (query.response_records && Array.isArray(query.response_records) && query.response_records.length > 0) {
var maxDisplay = 5; // 最多显示5条 var maxDisplay = 5; // 最多显示5条
var displayRecords = query.response_ips.slice(0, maxDisplay); var displayRecords = query.response_records.slice(0, maxDisplay);
var fullRecords = query.response_ips; var fullRecords = query.response_records;
return { return {
display: displayRecords, display: displayRecords,
@@ -1113,20 +1113,25 @@ return view.extend({
]), ]),
E('div', { 'class': 'stats-card' }, [ E('div', { 'class': 'stats-card' }, [
E('div', { 'class': 'stats-card-title' }, getTranslation('响应时间', language)), E('div', { 'class': 'stats-card-title' }, getTranslation('响应时间', language)),
E('div', { 'class': 'stats-card-value', 'id': 'stat-avg-response-time', 'style': 'margin-bottom: 12px;' }, [ E('div', { 'class': 'stats-card-value', 'id': 'stat-latest-response-time', 'style': 'margin-bottom: 12px;' }, [
E('span', {}, (stats.avg_response_time_ms || 0).toFixed(1)), E('span', {}, (stats.latest_response_time_ms || 0).toFixed(1)),
E('span', { 'class': 'stats-card-unit' }, ' ' + getTranslation('毫秒', language)) E('span', { 'class': 'stats-card-unit' }, ' ' + getTranslation('毫秒', language))
]), ]),
E('div', { 'class': 'stats-card-details', 'id': 'stat-response-time-details' }, [ E('div', { 'class': 'stats-card-details', 'id': 'stat-response-time-details' }, [
E('div', { 'class': 'stats-detail-row' }, [
E('span', { 'class': 'stats-detail-label' }, getTranslation('平均响应时间', language) + ':'),
E('span', { 'class': 'stats-detail-value', 'id': 'stat-avg-response-time' },
(stats.avg_response_time_ms || 0).toFixed(1) + ' ' + getTranslation('毫秒', language))
]),
E('div', { 'class': 'stats-detail-row' }, [ E('div', { 'class': 'stats-detail-row' }, [
E('span', { 'class': 'stats-detail-label' }, getTranslation('最快响应时间', language) + ':'), E('span', { 'class': 'stats-detail-label' }, getTranslation('最快响应时间', language) + ':'),
E('span', { 'class': 'stats-detail-value', 'id': 'stat-min-response-time' }, E('span', { 'class': 'stats-detail-value', 'id': 'stat-min-response-time' },
(stats.min_response_time_ms || 0) + ' ' + getTranslation('毫秒', language)) (stats.min_response_time_ms || 0).toFixed(1) + ' ' + getTranslation('毫秒', language))
]), ]),
E('div', { 'class': 'stats-detail-row' }, [ E('div', { 'class': 'stats-detail-row' }, [
E('span', { 'class': 'stats-detail-label' }, getTranslation('最慢响应时间', language) + ':'), E('span', { 'class': 'stats-detail-label' }, getTranslation('最慢响应时间', language) + ':'),
E('span', { 'class': 'stats-detail-value', 'id': 'stat-max-response-time' }, E('span', { 'class': 'stats-detail-value', 'id': 'stat-max-response-time' },
(stats.max_response_time_ms || 0) + ' ' + getTranslation('毫秒', language)) (stats.max_response_time_ms || 0).toFixed(1) + ' ' + getTranslation('毫秒', language))
]) ])
]) ])
]), ]),
@@ -1157,19 +1162,24 @@ return view.extend({
totalQueriesEl.textContent = stats.total_queries || 0; totalQueriesEl.textContent = stats.total_queries || 0;
} }
var latestResponseTimeEl = document.getElementById('stat-latest-response-time');
if (latestResponseTimeEl && latestResponseTimeEl.firstChild) {
latestResponseTimeEl.firstChild.textContent = (stats.latest_response_time_ms || 0).toFixed(1);
}
var avgResponseTimeEl = document.getElementById('stat-avg-response-time'); var avgResponseTimeEl = document.getElementById('stat-avg-response-time');
if (avgResponseTimeEl && avgResponseTimeEl.firstChild) { if (avgResponseTimeEl) {
avgResponseTimeEl.firstChild.textContent = (stats.avg_response_time_ms || 0).toFixed(1); avgResponseTimeEl.textContent = (stats.avg_response_time_ms || 0).toFixed(1) + ' ' + getTranslation('毫秒', language);
} }
var minResponseTimeEl = document.getElementById('stat-min-response-time'); var minResponseTimeEl = document.getElementById('stat-min-response-time');
if (minResponseTimeEl) { if (minResponseTimeEl) {
minResponseTimeEl.textContent = (stats.min_response_time_ms || 0) + ' ' + getTranslation('毫秒', language); minResponseTimeEl.textContent = (stats.min_response_time_ms || 0).toFixed(1) + ' ' + getTranslation('毫秒', language);
} }
var maxResponseTimeEl = document.getElementById('stat-max-response-time'); var maxResponseTimeEl = document.getElementById('stat-max-response-time');
if (maxResponseTimeEl) { if (maxResponseTimeEl) {
maxResponseTimeEl.textContent = (stats.max_response_time_ms || 0) + ' ' + getTranslation('毫秒', language); maxResponseTimeEl.textContent = (stats.max_response_time_ms || 0).toFixed(1) + ' ' + getTranslation('毫秒', language);
} }
var successRateEl = document.getElementById('stat-success-rate'); var successRateEl = document.getElementById('stat-success-rate');

View File

@@ -41,6 +41,8 @@ const translations = {
'明亮模式': '明亮模式', '明亮模式': '明亮模式',
'暗黑模式': '暗黑模式', '暗黑模式': '暗黑模式',
'意见反馈': '意见反馈', '意见反馈': '意见反馈',
'日志级别': '日志级别',
'设置 Bandix 服务的日志级别': '设置 Bandix 服务的日志级别',
'离线超时时间': '离线超时时间', '离线超时时间': '离线超时时间',
'设置设备离线判断的超时时间(秒)': '设置设备离线判断的超时时间(秒)。超过此时间未活动的设备将被标记为离线', '设置设备离线判断的超时时间(秒)': '设置设备离线判断的超时时间(秒)。超过此时间未活动的设备将被标记为离线',
'历史流量周期': '历史流量周期', '历史流量周期': '历史流量周期',
@@ -100,6 +102,8 @@ const translations = {
'明亮模式': '明亮模式', '明亮模式': '明亮模式',
'暗黑模式': '暗黑模式', '暗黑模式': '暗黑模式',
'意见反馈': '意見反饋', '意见反馈': '意見反饋',
'日志级别': '日誌級別',
'设置 Bandix 服务的日志级别': '設置 Bandix 服務的日誌級別',
'离线超时时间': '離線超時時間', '离线超时时间': '離線超時時間',
'设置设备离线判断的超时时间(秒)': '設定設備離線判斷的超時時間(秒)。超過此時間未活動的設備將被標記為離線', '设置设备离线判断的超时时间(秒)': '設定設備離線判斷的超時時間(秒)。超過此時間未活動的設備將被標記為離線',
'历史流量周期': '歷史流量週期', '历史流量周期': '歷史流量週期',
@@ -160,6 +164,8 @@ const translations = {
'明亮模式': 'Light Mode', '明亮模式': 'Light Mode',
'暗黑模式': 'Dark Mode', '暗黑模式': 'Dark Mode',
'意见反馈': 'Feedback', '意见反馈': 'Feedback',
'日志级别': 'Log Level',
'设置 Bandix 服务的日志级别': 'Set the log level for Bandix service',
'离线超时时间': 'Offline Timeout', '离线超时时间': 'Offline Timeout',
'设置设备离线判断的超时时间(秒)': 'Set the timeout for device offline detection (seconds). Devices inactive for longer than this time will be marked as offline', '设置设备离线判断的超时时间(秒)': 'Set the timeout for device offline detection (seconds). Devices inactive for longer than this time will be marked as offline',
'历史流量周期': 'Traffic History Period', '历史流量周期': 'Traffic History Period',
@@ -220,6 +226,8 @@ const translations = {
'明亮模式': 'Mode Clair', '明亮模式': 'Mode Clair',
'暗黑模式': 'Mode Sombre', '暗黑模式': 'Mode Sombre',
'意见反馈': 'Commentaires', '意见反馈': 'Commentaires',
'日志级别': 'Niveau de Journal',
'设置 Bandix 服务的日志级别': 'Définir le niveau de journal pour le service Bandix',
'离线超时时间': 'Délai d\'expiration hors ligne', '离线超时时间': 'Délai d\'expiration hors ligne',
'设置设备离线判断的超时时间(秒)': 'Définir le délai d\'expiration pour la détection hors ligne des appareils (secondes). Les appareils inactifs plus longtemps que cette durée seront marqués comme hors ligne', '设置设备离线判断的超时时间(秒)': 'Définir le délai d\'expiration pour la détection hors ligne des appareils (secondes). Les appareils inactifs plus longtemps que cette durée seront marqués comme hors ligne',
'历史流量周期': 'Période d\'Historique du Trafic', '历史流量周期': 'Période d\'Historique du Trafic',
@@ -280,6 +288,8 @@ const translations = {
'明亮模式': 'ライトモード', '明亮模式': 'ライトモード',
'暗黑模式': 'ダークモード', '暗黑模式': 'ダークモード',
'意见反馈': 'フィードバック', '意见反馈': 'フィードバック',
'日志级别': 'ログレベル',
'设置 Bandix 服务的日志级别': 'Bandix サービスのログレベルを設定',
'离线超时时间': 'オフラインタイムアウト', '离线超时时间': 'オフラインタイムアウト',
'设置设备离线判断的超时时间(秒)': 'デバイスのオフライン検出のタイムアウト時間(秒)を設定。この時間を超えて非アクティブなデバイスはオフラインとしてマークされます', '设置设备离线判断的超时时间(秒)': 'デバイスのオフライン検出のタイムアウト時間(秒)を設定。この時間を超えて非アクティブなデバイスはオフラインとしてマークされます',
'历史流量周期': 'トラフィック履歴期間', '历史流量周期': 'トラフィック履歴期間',
@@ -340,6 +350,8 @@ const translations = {
'明亮模式': 'Светлый Режим', '明亮模式': 'Светлый Режим',
'暗黑模式': 'Темный Режим', '暗黑模式': 'Темный Режим',
'意见反馈': 'Обратная связь', '意见反馈': 'Обратная связь',
'日志级别': 'Уровень Журналирования',
'设置 Bandix 服务的日志级别': 'Установить уровень журналирования для службы Bandix',
'离线超时时间': 'Таймаут отключения', '离线超时时间': 'Таймаут отключения',
'设置设备离线判断的超时时间(秒)': 'Установить таймаут для обнаружения отключения устройств (секунды). Устройства, неактивные дольше этого времени, будут помечены как отключенные', '设置设备离线判断的超时时间(秒)': 'Установить таймаут для обнаружения отключения устройств (секунды). Устройства, неактивные дольше этого времени, будут помечены как отключенные',
'历史流量周期': 'Период Истории Трафика', '历史流量周期': 'Период Истории Трафика',
@@ -599,6 +611,7 @@ return view.extend({
o.placeholder = '8686'; o.placeholder = '8686';
o.rmempty = false; o.rmempty = false;
// 添加网卡选择下拉菜单 // 添加网卡选择下拉菜单
o = s.option(form.ListValue, 'iface', getTranslation('监控网卡', language), o = s.option(form.ListValue, 'iface', getTranslation('监控网卡', language),
getTranslation('选择要监控的网络接口', language)); getTranslation('选择要监控的网络接口', language));
@@ -631,6 +644,18 @@ return view.extend({
o.default = 'auto'; o.default = 'auto';
o.rmempty = false; o.rmempty = false;
// 添加日志级别选择选项
o = s.option(form.ListValue, 'log_level', getTranslation('日志级别', language),
getTranslation('设置 Bandix 服务的日志级别', language));
o.value('trace', 'Trace');
o.value('debug', 'Debug');
o.value('info', 'Info');
o.value('warn', 'Warn');
o.value('error', 'Error');
o.default = 'info';
o.rmempty = false;
// 添加数据目录设置(只读) // 添加数据目录设置(只读)
o = s.option(form.DummyValue, 'data_dir', getTranslation('数据目录', language)); o = s.option(form.DummyValue, 'data_dir', getTranslation('数据目录', language));
o.default = '/usr/share/bandix'; o.default = '/usr/share/bandix';

View File

@@ -6,7 +6,7 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall PKG_NAME:=luci-app-passwall
PKG_VERSION:=25.11.1 PKG_VERSION:=25.11.14
PKG_RELEASE:=1 PKG_RELEASE:=1
PKG_PO_VERSION:=$(PKG_VERSION) PKG_PO_VERSION:=$(PKG_VERSION)

View File

@@ -109,8 +109,10 @@ end
--check excluded domain --check excluded domain
local function check_excluded_domain(value) local function check_excluded_domain(value)
for k,v in ipairs(excluded_domain) do value = value and value:lower() or ""
if value:find(v) then for _, domain in ipairs(excluded_domain) do
local pattern = "^[a-z0-9-]+%.(" .. domain .. ")$"
if value:match(pattern) then
return true return true
end end
end end

View File

@@ -5,7 +5,7 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall2 PKG_NAME:=luci-app-passwall2
PKG_VERSION:=25.11.2 PKG_VERSION:=25.11.14
PKG_RELEASE:=1 PKG_RELEASE:=1
PKG_PO_VERSION:=$(PKG_VERSION) PKG_PO_VERSION:=$(PKG_VERSION)

View File

@@ -129,7 +129,7 @@ function hide_menu()
end end
function link_add_node() function link_add_node()
-- 分片接收以突破uhttpd的限制 -- Fragmented reception to overcome uhttpd limitations
local tmp_file = "/tmp/links.conf" local tmp_file = "/tmp/links.conf"
local chunk = http.formvalue("chunk") local chunk = http.formvalue("chunk")
local chunk_index = tonumber(http.formvalue("chunk_index")) local chunk_index = tonumber(http.formvalue("chunk_index"))
@@ -137,7 +137,7 @@ function link_add_node()
local group = http.formvalue("group") or "default" local group = http.formvalue("group") or "default"
if chunk and chunk_index ~= nil and total_chunks ~= nil then if chunk and chunk_index ~= nil and total_chunks ~= nil then
-- 按顺序拼接到文件 -- Assemble the files in order
local mode = "a" local mode = "a"
if chunk_index == 0 then if chunk_index == 0 then
mode = "w" mode = "w"
@@ -147,7 +147,7 @@ function link_add_node()
f:write(chunk) f:write(chunk)
f:close() f:close()
end end
-- 如果是最后一片,才执行 -- If it's the last piece, then it will be executed.
if chunk_index + 1 == total_chunks then if chunk_index + 1 == total_chunks then
luci.sys.call("lua /usr/share/passwall2/subscribe.lua add " .. group) luci.sys.call("lua /usr/share/passwall2/subscribe.lua add " .. group)
end end
@@ -624,7 +624,7 @@ function restore_backup()
fp:close() fp:close()
if chunk_index + 1 == total_chunks then if chunk_index + 1 == total_chunks then
api.sys.call("echo '' > /tmp/log/passwall2.log") api.sys.call("echo '' > /tmp/log/passwall2.log")
api.log(" * PassWall2 配置文件上传成功") api.log(string.format(" * PassWall2 %s", i18n.translate("Configuration file uploaded successfully")))
local temp_dir = '/tmp/passwall2_bak' local temp_dir = '/tmp/passwall2_bak'
api.sys.call("mkdir -p " .. temp_dir) api.sys.call("mkdir -p " .. temp_dir)
if api.sys.call("tar -xzf " .. file_path .. " -C " .. temp_dir) == 0 then if api.sys.call("tar -xzf " .. file_path .. " -C " .. temp_dir) == 0 then
@@ -634,13 +634,13 @@ function restore_backup()
api.sys.call("cp -f " .. temp_file .. " " .. backup_file) api.sys.call("cp -f " .. temp_file .. " " .. backup_file)
end end
end end
api.log(" * PassWall2 配置还原成功") api.log(string.format(" * PassWall2 %s", i18n.translate("Configuration restored successfully")))
api.log(" * 重启 PassWall2 服务中…\n") api.log(string.format(" * PassWall2 %s", i18n.translate("Service restarting…")))
luci.sys.call('/etc/init.d/passwall2 restart > /dev/null 2>&1 &') luci.sys.call('/etc/init.d/passwall2 restart > /dev/null 2>&1 &')
luci.sys.call('/etc/init.d/passwall2_server restart > /dev/null 2>&1 &') luci.sys.call('/etc/init.d/passwall2_server restart > /dev/null 2>&1 &')
result = { status = "success", message = "Upload completed", path = file_path } result = { status = "success", message = "Upload completed", path = file_path }
else else
api.log(" * PassWall2 配置文件解压失败,请重试!") api.log(string.format(" * PassWall2 %s", i18n.translate("Configuration file decompression failed, please try again!")))
result = { status = "error", message = "Decompression failed" } result = { status = "error", message = "Decompression failed" }
end end
api.sys.call("rm -rf " .. temp_dir) api.sys.call("rm -rf " .. temp_dir)
@@ -746,7 +746,7 @@ function subscribe_manual_all()
end end
local section_list = util.split(sections, ",") local section_list = util.split(sections, ",")
local url_list = util.split(urls, ",") local url_list = util.split(urls, ",")
-- 检查是否存在未保存配置 -- Check if there are any unsaved configurations.
for i, section in ipairs(section_list) do for i, section in ipairs(section_list) do
local uci_url = api.sh_uci_get(appname, section, "url") local uci_url = api.sh_uci_get(appname, section, "url")
if not uci_url or uci_url == "" then if not uci_url or uci_url == "" then
@@ -754,7 +754,7 @@ function subscribe_manual_all()
return return
end end
end end
-- 保存有变动的url -- Save URLs that have changed.
for i, section in ipairs(section_list) do for i, section in ipairs(section_list) do
local current_url = url_list[i] or "" local current_url = url_list[i] or ""
local uci_url = api.sh_uci_get(appname, section, "url") local uci_url = api.sh_uci_get(appname, section, "url")

View File

@@ -40,7 +40,7 @@ m.uci:foreach(appname, "socks", function(s)
if s.enabled == "1" and s.node then if s.enabled == "1" and s.node then
socks_list[#socks_list + 1] = { socks_list[#socks_list + 1] = {
id = "Socks_" .. s[".name"], id = "Socks_" .. s[".name"],
remark = translate("Socks Config") .. " [" .. s.port .. "端口]" remark = translate("Socks Config") .. " [" .. s.port .. translate("Port") .. "]"
} }
end end
end) end)
@@ -84,7 +84,7 @@ o.rmempty = false
o = s:taboption("Main", ListValue, "node", "<a style='color: red'>" .. translate("Node") .. "</a>") o = s:taboption("Main", ListValue, "node", "<a style='color: red'>" .. translate("Node") .. "</a>")
o:value("", translate("Close")) o:value("", translate("Close"))
-- 分流 -- Shunt
if (has_singbox or has_xray) and #nodes_table > 0 then if (has_singbox or has_xray) and #nodes_table > 0 then
local function get_cfgvalue(shunt_node_id, option) local function get_cfgvalue(shunt_node_id, option)
return function(self, section) return function(self, section)
@@ -152,7 +152,7 @@ if (has_singbox or has_xray) and #nodes_table > 0 then
if (has_singbox and has_xray) or (v.type == "sing-box" and not has_singbox) or (v.type == "Xray" and not has_xray) then if (has_singbox and has_xray) or (v.type == "sing-box" and not has_singbox) or (v.type == "Xray" and not has_xray) then
type:depends("node", v.id) type:depends("node", v.id)
else else
type:depends({ __hide = true }) --不存在的依赖,即始终隐藏 type:depends({ __hide = true }) -- Always hidden.
end end
m.uci:foreach(appname, "shunt_rules", function(e) m.uci:foreach(appname, "shunt_rules", function(e)

View File

@@ -36,6 +36,21 @@ end)
for k, v in pairs(groups) do for k, v in pairs(groups) do
o:value(k) o:value(k)
end 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
local fs = require "nixio.fs" local fs = require "nixio.fs"
local types_dir = "/usr/lib/lua/luci/model/cbi/passwall2/client/type/" local types_dir = "/usr/lib/lua/luci/model/cbi/passwall2/client/type/"

View File

@@ -168,19 +168,33 @@ end
o = s:option(Value, "remark", translate("Remarks")) o = s:option(Value, "remark", translate("Remarks"))
o.width = "auto" o.width = "auto"
o.rmempty = false o.rmempty = false
o.validate = function(self, value, t) o.validate = function(self, value, section)
if value then value = api.trim(value)
local count = 0 if value == "" then
return nil, translate("Remark cannot be empty.")
end
local duplicate = false
m.uci:foreach(appname, "subscribe_list", function(e) m.uci:foreach(appname, "subscribe_list", function(e)
if e[".name"] ~= t and e["remark"] == value then if e[".name"] ~= section and e["remark"] and e["remark"]:lower() == value:lower() then
count = count + 1 duplicate = true
return false
end end
end) end)
if count > 0 then if duplicate or value:lower() == "default" then
return nil, translate("This remark already exists, please change a new remark.") return nil, translate("This remark already exists, please change a new remark.")
end end
return value return value
end end
o.write = function(self, section, value)
local old = m:get(section, self.option) or ""
if old:lower() ~= value:lower() then
m.uci:foreach(appname, "nodes", function(e)
if e["group"] and e["group"]:lower() == old:lower() then
m.uci:set(appname, e[".name"], "group", value)
end
end)
end
return Value.write(self, section, value)
end end
o = s:option(DummyValue, "_node_count", translate("Subscribe Info")) o = s:option(DummyValue, "_node_count", translate("Subscribe Info"))

View File

@@ -74,13 +74,41 @@ end
o = s:option(Value, "remark", translate("Subscribe Remark")) o = s:option(Value, "remark", translate("Subscribe Remark"))
o.rmempty = false o.rmempty = false
o.validate = function(self, value, section)
value = api.trim(value)
if value == "" then
return nil, translate("Remark cannot be empty.")
end
local duplicate = false
m.uci:foreach(appname, "subscribe_list", function(e)
if e[".name"] ~= section and e["remark"] and e["remark"]:lower() == value:lower() then
duplicate = true
return false
end
end)
if duplicate or value:lower() == "default" then
return nil, translate("This remark already exists, please change a new remark.")
end
return value
end
o.write = function(self, section, value)
local old = m:get(section, self.option) or ""
if old:lower() ~= value:lower() then
m.uci:foreach(appname, "nodes", function(e)
if e["group"] and e["group"]:lower() == old:lower() then
m.uci:set(appname, e[".name"], "group", value)
end
end)
end
return Value.write(self, section, value)
end
o = s:option(TextValue, "url", translate("Subscribe URL")) o = s:option(TextValue, "url", translate("Subscribe URL"))
o.rows = 5 o.rows = 5
o.rmempty = false o.rmempty = false
o.validate = function(self, value) o.validate = function(self, value)
if not value or value == "" then if not value or value == "" then
return nil, translate("URL cannot be empty") return nil, translate("URL cannot be empty.")
end end
return value:gsub("%s+", ""):gsub("%z", "") return value:gsub("%s+", ""):gsub("%z", "")
end end

View File

@@ -119,7 +119,7 @@ if (os.execute("lsmod | grep -i REDIRECT >/dev/null") == 0 and os.execute("lsmod
o:value("tproxy", "TPROXY") o:value("tproxy", "TPROXY")
o:depends("ipv6_tproxy", false) o:depends("ipv6_tproxy", false)
o.remove = function(self, section) o.remove = function(self, section)
-- 禁止在隐藏时删除 -- Do not delete while hidden
end end
o = s:option(ListValue, "_tcp_proxy_way", translate("TCP Proxy Way")) o = s:option(ListValue, "_tcp_proxy_way", translate("TCP Proxy Way"))

View File

@@ -27,8 +27,8 @@ div[id^="cbid.passwall2."] .cbi-checkbox {
]] ]]
function clean_text(text) function clean_text(text)
local nbsp = string.char(0xC2, 0xA0) -- 不间断空格(U+00A0 local nbsp = string.char(0xC2, 0xA0) -- Non-breaking space (U+00A0)
local fullwidth_space = string.char(0xE3, 0x80, 0x80) -- 全角空格(U+3000 local fullwidth_space = string.char(0xE3, 0x80, 0x80) -- Full-width space (U+3000)
return text return text
:gsub("\t", " ") :gsub("\t", " ")
:gsub(nbsp, " ") :gsub(nbsp, " ")

View File

@@ -95,12 +95,12 @@ m.uci:foreach(appname, "socks", function(s)
if s.enabled == "1" and s.node then if s.enabled == "1" and s.node then
socks_list[#socks_list + 1] = { socks_list[#socks_list + 1] = {
id = "Socks_" .. s[".name"], id = "Socks_" .. s[".name"],
remark = translate("Socks Config") .. " [" .. s.port .. "端口]" remark = translate("Socks Config") .. " [" .. s.port .. translate("Port") .. "]"
} }
end end
end) end)
-- 负载均衡列表 -- Load balancing node list
o = s:option(DynamicList, _n("balancing_node"), translate("Load balancing node list"), translate("Load balancing node list, <a target='_blank' href='https://xtls.github.io/config/routing.html#balancerobject'>document</a>")) o = s:option(DynamicList, _n("balancing_node"), translate("Load balancing node list"), translate("Load balancing node list, <a target='_blank' href='https://xtls.github.io/config/routing.html#balancerobject'>document</a>"))
o:depends({ [_n("protocol")] = "_balancing" }) o:depends({ [_n("protocol")] = "_balancing" })
local valid_ids = {} local valid_ids = {}
@@ -108,7 +108,7 @@ for k, v in pairs(nodes_table) do
o:value(v.id, v.remark) o:value(v.id, v.remark)
valid_ids[v.id] = true valid_ids[v.id] = true
end end
-- 去重并禁止自定义非法输入 -- Deduplication and disabling of custom and illegal input
function o.custom_write(self, section, value) function o.custom_write(self, section, value)
local result = {} local result = {}
if type(value) == "table" then if type(value) == "table" then
@@ -145,14 +145,13 @@ local function check_fallback_chain(fb)
end end
end end
end end
-- 检查fallback链去掉会形成闭环的balancer节点 -- Check the fallback chain and remove the balancer node that would form a closed loop.
if is_balancer then if is_balancer then
check_fallback_chain(arg[1]) check_fallback_chain(arg[1])
end end
for k, v in pairs(fallback_table) do o:value(v.id, v.remark) end for k, v in pairs(fallback_table) do o:value(v.id, v.remark) end
for k, v in pairs(nodes_table) do o:value(v.id, v.remark) end for k, v in pairs(nodes_table) do o:value(v.id, v.remark) end
-- 探测地址
o = s:option(Flag, _n("useCustomProbeUrl"), translate("Use Custom Probe URL"), translate("By default the built-in probe URL will be used, enable this option to use a custom probe URL.")) o = s:option(Flag, _n("useCustomProbeUrl"), translate("Use Custom Probe URL"), translate("By default the built-in probe URL will be used, enable this option to use a custom probe URL."))
o:depends({ [_n("protocol")] = "_balancing" }) o:depends({ [_n("protocol")] = "_balancing" })
@@ -167,7 +166,6 @@ o:value("https://connectivitycheck.platform.hicloud.com/generate_204", "HiCloud
o.default = "https://www.google.com/generate_204" o.default = "https://www.google.com/generate_204"
o.description = translate("The URL used to detect the connection status.") o.description = translate("The URL used to detect the connection status.")
-- 探测间隔
o = s:option(Value, _n("probeInterval"), translate("Probe Interval")) o = s:option(Value, _n("probeInterval"), translate("Probe Interval"))
o:depends({ [_n("protocol")] = "_balancing" }) o:depends({ [_n("protocol")] = "_balancing" })
o.default = "1m" o.default = "1m"
@@ -184,7 +182,7 @@ o.placeholder = "2"
o.description = translate("The load balancer selects the optimal number of nodes, and traffic is randomly distributed among them.") o.description = translate("The load balancer selects the optimal number of nodes, and traffic is randomly distributed among them.")
-- [[ 分流模块 ]] -- [[ Shunt Start ]]
if #nodes_table > 0 then if #nodes_table > 0 then
o = s:option(Flag, _n("preproxy_enabled"), translate("Preproxy")) o = s:option(Flag, _n("preproxy_enabled"), translate("Preproxy"))
o:depends({ [_n("protocol")] = "_shunt" }) o:depends({ [_n("protocol")] = "_shunt" })
@@ -285,7 +283,7 @@ o:value("hybrid")
o:value("linear") o:value("linear")
o:depends({ [_n("protocol")] = "_shunt" }) o:depends({ [_n("protocol")] = "_shunt" })
-- [[ 分流模块 End ]] -- [[ Shunt End ]]
o = s:option(Value, _n("address"), translate("Address (Support Domain Name)")) o = s:option(Value, _n("address"), translate("Address (Support Domain Name)"))
@@ -412,7 +410,7 @@ o:value("half")
o:value("full") o:value("full")
o:depends({ [_n("ech")] = true }) o:depends({ [_n("ech")] = true })
-- [[ REALITY部分 ]] -- -- [[ REALITY ]] --
o = s:option(Value, _n("reality_publicKey"), translate("Public Key")) o = s:option(Value, _n("reality_publicKey"), translate("Public Key"))
o:depends({ [_n("tls")] = true, [_n("reality")] = true }) o:depends({ [_n("tls")] = true, [_n("reality")] = true })
@@ -493,24 +491,21 @@ o = s:option(Value, _n("wireguard_keepAlive"), translate("Keep Alive"))
o.default = "0" o.default = "0"
o:depends({ [_n("protocol")] = "wireguard" }) o:depends({ [_n("protocol")] = "wireguard" })
-- [[ RAW部分 ]]-- -- [[ RAW ]]--
-- TCP伪装
o = s:option(ListValue, _n("tcp_guise"), translate("Camouflage Type")) o = s:option(ListValue, _n("tcp_guise"), translate("Camouflage Type"))
o:value("none", "none") o:value("none", "none")
o:value("http", "http") o:value("http", "http")
o:depends({ [_n("transport")] = "raw" }) o:depends({ [_n("transport")] = "raw" })
-- HTTP域名
o = s:option(DynamicList, _n("tcp_guise_http_host"), translate("HTTP Host")) o = s:option(DynamicList, _n("tcp_guise_http_host"), translate("HTTP Host"))
o:depends({ [_n("tcp_guise")] = "http" }) o:depends({ [_n("tcp_guise")] = "http" })
-- HTTP路径
o = s:option(DynamicList, _n("tcp_guise_http_path"), translate("HTTP Path")) o = s:option(DynamicList, _n("tcp_guise_http_path"), translate("HTTP Path"))
o.placeholder = "/" o.placeholder = "/"
o:depends({ [_n("tcp_guise")] = "http" }) o:depends({ [_n("tcp_guise")] = "http" })
-- [[ mKCP部分 ]]-- -- [[ mKCP ]]--
o = s:option(ListValue, _n("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)<br />dns: Disguising traffic as DNS requests.')) o = s:option(ListValue, _n("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)<br />dns: Disguising traffic as DNS requests.'))
for a, t in ipairs(header_type_list) do o:value(t) end for a, t in ipairs(header_type_list) do o:value(t) end
@@ -549,7 +544,7 @@ o:depends({ [_n("transport")] = "mkcp" })
o = s:option(Value, _n("mkcp_seed"), translate("KCP Seed")) o = s:option(Value, _n("mkcp_seed"), translate("KCP Seed"))
o:depends({ [_n("transport")] = "mkcp" }) o:depends({ [_n("transport")] = "mkcp" })
-- [[ WebSocket部分 ]]-- -- [[ WebSocket ]]--
o = s:option(Value, _n("ws_host"), translate("WebSocket Host")) o = s:option(Value, _n("ws_host"), translate("WebSocket Host"))
o:depends({ [_n("transport")] = "ws" }) o:depends({ [_n("transport")] = "ws" })
@@ -561,7 +556,7 @@ o = s:option(Value, _n("ws_heartbeatPeriod"), translate("HeartbeatPeriod(second)
o.datatype = "integer" o.datatype = "integer"
o:depends({ [_n("transport")] = "ws" }) o:depends({ [_n("transport")] = "ws" })
-- [[ gRPC部分 ]]-- -- [[ gRPC ]]--
o = s:option(Value, _n("grpc_serviceName"), "ServiceName") o = s:option(Value, _n("grpc_serviceName"), "ServiceName")
o:depends({ [_n("transport")] = "grpc" }) o:depends({ [_n("transport")] = "grpc" })
@@ -589,7 +584,7 @@ o = s:option(Value, _n("grpc_initial_windows_size"), translate("Initial Windows
o.default = "0" o.default = "0"
o:depends({ [_n("transport")] = "grpc" }) o:depends({ [_n("transport")] = "grpc" })
-- [[ HttpUpgrade部分 ]]-- -- [[ HttpUpgrade ]]--
o = s:option(Value, _n("httpupgrade_host"), translate("HttpUpgrade Host")) o = s:option(Value, _n("httpupgrade_host"), translate("HttpUpgrade Host"))
o:depends({ [_n("transport")] = "httpupgrade" }) o:depends({ [_n("transport")] = "httpupgrade" })
@@ -597,7 +592,7 @@ o = s:option(Value, _n("httpupgrade_path"), translate("HttpUpgrade Path"))
o.placeholder = "/" o.placeholder = "/"
o:depends({ [_n("transport")] = "httpupgrade" }) o:depends({ [_n("transport")] = "httpupgrade" })
-- [[ XHTTP部分 ]]-- -- [[ XHTTP ]]--
o = s:option(ListValue, _n("xhttp_mode"), "XHTTP " .. translate("Mode")) o = s:option(ListValue, _n("xhttp_mode"), "XHTTP " .. translate("Mode"))
o:depends({ [_n("transport")] = "xhttp" }) o:depends({ [_n("transport")] = "xhttp" })
o.default = "auto" o.default = "auto"

View File

@@ -105,7 +105,7 @@ m.uci:foreach(appname, "socks", function(s)
if s.enabled == "1" and s.node then if s.enabled == "1" and s.node then
socks_list[#socks_list + 1] = { socks_list[#socks_list + 1] = {
id = "Socks_" .. s[".name"], id = "Socks_" .. s[".name"],
remark = translate("Socks Config") .. " [" .. s.port .. "端口]" remark = translate("Socks Config") .. " [" .. s.port .. translate("Port") .. "]"
} }
end end
end) end)
@@ -118,7 +118,7 @@ for k, v in pairs(nodes_table) do
o:value(v.id, v.remark) o:value(v.id, v.remark)
valid_ids[v.id] = true valid_ids[v.id] = true
end end
-- 去重并禁止自定义非法输入 -- Deduplication and disabling of custom and illegal input
function o.custom_write(self, section, value) function o.custom_write(self, section, value)
local result = {} local result = {}
if type(value) == "table" then if type(value) == "table" then
@@ -174,7 +174,7 @@ o:depends({ [_n("protocol")] = "_urltest" })
o.default = "0" o.default = "0"
o.description = translate("Interrupt existing connections when the selected outbound has changed.") o.description = translate("Interrupt existing connections when the selected outbound has changed.")
-- [[ 分流模块 ]] -- [[ Shunt Start ]]
if #nodes_table > 0 then if #nodes_table > 0 then
o = s:option(Flag, _n("preproxy_enabled"), translate("Preproxy")) o = s:option(Flag, _n("preproxy_enabled"), translate("Preproxy"))
o:depends({ [_n("protocol")] = "_shunt" }) o:depends({ [_n("protocol")] = "_shunt" })
@@ -259,7 +259,7 @@ if #nodes_table > 0 then
end end
end end
-- [[ 分流模块 End ]] -- [[ Shunt End ]]
o = s:option(Value, _n("address"), translate("Address (Support Domain Name)")) o = s:option(Value, _n("address"), translate("Address (Support Domain Name)"))
@@ -587,7 +587,7 @@ if singbox_tags:find("with_utls") then
o.default = "chrome" o.default = "chrome"
o:depends({ [_n("utls")] = true }) o:depends({ [_n("utls")] = true })
-- [[ REALITY部分 ]] -- -- [[ REALITY ]] --
o = s:option(Flag, _n("reality"), translate("REALITY")) o = s:option(Flag, _n("reality"), translate("REALITY"))
o.default = 0 o.default = 0
o:depends({ [_n("protocol")] = "vless", [_n("tls")] = true }) o:depends({ [_n("protocol")] = "vless", [_n("tls")] = true })
@@ -650,7 +650,7 @@ if singbox_tags:find("with_wireguard") then
o:depends({ [_n("protocol")] = "wireguard" }) o:depends({ [_n("protocol")] = "wireguard" })
end end
-- [[ TCP部分(模拟) ]]-- -- [[ TCP ]]--
o = s:option(ListValue, _n("tcp_guise"), translate("Camouflage Type")) o = s:option(ListValue, _n("tcp_guise"), translate("Camouflage Type"))
o:value("none", "none") o:value("none", "none")
o:value("http", "http") o:value("http", "http")
@@ -663,7 +663,7 @@ o = s:option(DynamicList, _n("tcp_guise_http_path"), translate("HTTP Path"))
o.placeholder = "/" o.placeholder = "/"
o:depends({ [_n("tcp_guise")] = "http" }) o:depends({ [_n("tcp_guise")] = "http" })
-- [[ HTTP部分 ]]-- -- [[ HTTP ]]--
o = s:option(DynamicList, _n("http_host"), translate("HTTP Host")) o = s:option(DynamicList, _n("http_host"), translate("HTTP Host"))
o:depends({ [_n("transport")] = "http" }) o:depends({ [_n("transport")] = "http" })
@@ -682,7 +682,7 @@ o = s:option(Value, _n("http_h2_health_check_timeout"), translate("Health check
o.default = "15" o.default = "15"
o:depends({ [_n("tls")] = true, [_n("transport")] = "http", [_n("http_h2_health_check")] = true }) o:depends({ [_n("tls")] = true, [_n("transport")] = "http", [_n("http_h2_health_check")] = true })
-- [[ WebSocket部分 ]]-- -- [[ WebSocket ]]--
o = s:option(Value, _n("ws_host"), translate("WebSocket Host")) o = s:option(Value, _n("ws_host"), translate("WebSocket Host"))
o:depends({ [_n("transport")] = "ws" }) o:depends({ [_n("transport")] = "ws" })
@@ -700,7 +700,7 @@ o:depends({ [_n("ws_enableEarlyData")] = true })
o = s:option(Value, _n("ws_earlyDataHeaderName"), translate("Early data header name"), translate("Recommended value: Sec-WebSocket-Protocol")) o = s:option(Value, _n("ws_earlyDataHeaderName"), translate("Early data header name"), translate("Recommended value: Sec-WebSocket-Protocol"))
o:depends({ [_n("ws_enableEarlyData")] = true }) o:depends({ [_n("ws_enableEarlyData")] = true })
-- [[ HTTPUpgrade部分 ]]-- -- [[ HTTPUpgrade ]]--
o = s:option(Value, _n("httpupgrade_host"), translate("HTTPUpgrade Host")) o = s:option(Value, _n("httpupgrade_host"), translate("HTTPUpgrade Host"))
o:depends({ [_n("transport")] = "httpupgrade" }) o:depends({ [_n("transport")] = "httpupgrade" })
@@ -708,7 +708,7 @@ o = s:option(Value, _n("httpupgrade_path"), translate("HTTPUpgrade Path"))
o.placeholder = "/" o.placeholder = "/"
o:depends({ [_n("transport")] = "httpupgrade" }) o:depends({ [_n("transport")] = "httpupgrade" })
-- [[ gRPC部分 ]]-- -- [[ gRPC ]]--
o = s:option(Value, _n("grpc_serviceName"), "ServiceName") o = s:option(Value, _n("grpc_serviceName"), "ServiceName")
o:depends({ [_n("transport")] = "grpc" }) o:depends({ [_n("transport")] = "grpc" })

View File

@@ -147,7 +147,7 @@ o:depends({ [_n("protocol")] = "socks" })
o:depends({ [_n("protocol")] = "shadowsocks" }) o:depends({ [_n("protocol")] = "shadowsocks" })
o:depends({ [_n("protocol")] = "trojan" }) o:depends({ [_n("protocol")] = "trojan" })
-- [[ REALITY部分 ]] -- -- [[ REALITY ]] --
o = s:option(Flag, _n("reality"), translate("REALITY")) o = s:option(Flag, _n("reality"), translate("REALITY"))
o.default = 0 o.default = 0
o:depends({ [_n("tls")] = true }) o:depends({ [_n("tls")] = true })
@@ -211,7 +211,7 @@ end
-- o:value("1.3") -- o:value("1.3")
--o:depends({ [_n("tls")] = true }) --o:depends({ [_n("tls")] = true })
-- [[ TLS部分 ]] -- -- [[ TLS ]] --
o = s:option(FileUpload, _n("tls_certificateFile"), translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem") o = s:option(FileUpload, _n("tls_certificateFile"), translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem")
o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. arg[1] .. ".pem" o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. arg[1] .. ".pem"
if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end if o and o:formvalue(arg[1]) then o.default = o:formvalue(arg[1]) end
@@ -268,14 +268,14 @@ o:depends({ [_n("protocol")] = "socks" })
o:depends({ [_n("protocol")] = "shadowsocks" }) o:depends({ [_n("protocol")] = "shadowsocks" })
o:depends({ [_n("protocol")] = "trojan" }) o:depends({ [_n("protocol")] = "trojan" })
-- [[ WebSocket部分 ]]-- -- [[ WebSocket ]]--
o = s:option(Value, _n("ws_host"), translate("WebSocket Host")) o = s:option(Value, _n("ws_host"), translate("WebSocket Host"))
o:depends({ [_n("transport")] = "ws" }) o:depends({ [_n("transport")] = "ws" })
o = s:option(Value, _n("ws_path"), translate("WebSocket Path")) o = s:option(Value, _n("ws_path"), translate("WebSocket Path"))
o:depends({ [_n("transport")] = "ws" }) o:depends({ [_n("transport")] = "ws" })
-- [[ HttpUpgrade部分 ]]-- -- [[ HttpUpgrade ]]--
o = s:option(Value, _n("httpupgrade_host"), translate("HttpUpgrade Host")) o = s:option(Value, _n("httpupgrade_host"), translate("HttpUpgrade Host"))
o:depends({ [_n("transport")] = "httpupgrade" }) o:depends({ [_n("transport")] = "httpupgrade" })
@@ -283,7 +283,7 @@ o = s:option(Value, _n("httpupgrade_path"), translate("HttpUpgrade Path"))
o.placeholder = "/" o.placeholder = "/"
o:depends({ [_n("transport")] = "httpupgrade" }) o:depends({ [_n("transport")] = "httpupgrade" })
-- [[ XHTTP部分 ]]-- -- [[ XHTTP ]]--
o = s:option(Value, _n("xhttp_host"), translate("XHTTP Host")) o = s:option(Value, _n("xhttp_host"), translate("XHTTP Host"))
o:depends({ [_n("transport")] = "xhttp" }) o:depends({ [_n("transport")] = "xhttp" })
@@ -307,23 +307,20 @@ o = s:option(Value, _n("splithttp_maxconcurrentuploads"), translate("maxConcurre
o.default = "10" o.default = "10"
o:depends({ [_n("transport")] = "splithttp" }) o:depends({ [_n("transport")] = "splithttp" })
-- [[ TCP部分 ]]-- -- [[ TCP ]]--
-- TCP伪装
o = s:option(ListValue, _n("tcp_guise"), translate("Camouflage Type")) o = s:option(ListValue, _n("tcp_guise"), translate("Camouflage Type"))
o:value("none", "none") o:value("none", "none")
o:value("http", "http") o:value("http", "http")
o:depends({ [_n("transport")] = "raw" }) o:depends({ [_n("transport")] = "raw" })
-- HTTP域名
o = s:option(DynamicList, _n("tcp_guise_http_host"), translate("HTTP Host")) o = s:option(DynamicList, _n("tcp_guise_http_host"), translate("HTTP Host"))
o:depends({ [_n("tcp_guise")] = "http" }) o:depends({ [_n("tcp_guise")] = "http" })
-- HTTP路径
o = s:option(DynamicList, _n("tcp_guise_http_path"), translate("HTTP Path")) o = s:option(DynamicList, _n("tcp_guise_http_path"), translate("HTTP Path"))
o:depends({ [_n("tcp_guise")] = "http" }) o:depends({ [_n("tcp_guise")] = "http" })
-- [[ mKCP部分 ]]-- -- [[ mKCP ]]--
o = s:option(ListValue, _n("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)<br />dns: Disguising traffic as DNS requests.')) o = s:option(ListValue, _n("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)<br />dns: Disguising traffic as DNS requests.'))
for a, t in ipairs(header_type_list) do o:value(t) end for a, t in ipairs(header_type_list) do o:value(t) end
@@ -362,7 +359,7 @@ o:depends({ [_n("transport")] = "mkcp" })
o = s:option(Value, _n("mkcp_seed"), translate("KCP Seed")) o = s:option(Value, _n("mkcp_seed"), translate("KCP Seed"))
o:depends({ [_n("transport")] = "mkcp" }) o:depends({ [_n("transport")] = "mkcp" })
-- [[ gRPC部分 ]]-- -- [[ gRPC ]]--
o = s:option(Value, _n("grpc_serviceName"), "ServiceName") o = s:option(Value, _n("grpc_serviceName"), "ServiceName")
o:depends({ [_n("transport")] = "grpc" }) o:depends({ [_n("transport")] = "grpc" })
@@ -370,7 +367,7 @@ o = s:option(Flag, _n("acceptProxyProtocol"), translate("acceptProxyProtocol"),
o.default = "0" o.default = "0"
o:depends({ [_n("custom")] = false }) o:depends({ [_n("custom")] = false })
-- [[ Fallback部分 ]]-- -- [[ Fallback ]]--
o = s:option(Flag, _n("fallback"), translate("Fallback")) o = s:option(Flag, _n("fallback"), translate("Fallback"))
o:depends({ [_n("protocol")] = "vless", [_n("transport")] = "raw" }) o:depends({ [_n("protocol")] = "vless", [_n("transport")] = "raw" })
o:depends({ [_n("protocol")] = "trojan", [_n("transport")] = "raw" }) o:depends({ [_n("protocol")] = "trojan", [_n("transport")] = "raw" })

View File

@@ -239,7 +239,7 @@ o:depends({ [_n("protocol")] = "anytls" })
-- https://github.com/SagerNet/sing-box/commit/d2a04c4e41e6cef0937331cb6d10211f431caaab -- https://github.com/SagerNet/sing-box/commit/d2a04c4e41e6cef0937331cb6d10211f431caaab
if singbox_tags:find("with_utls") then if singbox_tags:find("with_utls") then
-- [[ REALITY部分 ]] -- -- [[ REALITY ]] --
o = s:option(Flag, _n("reality"), translate("REALITY")) o = s:option(Flag, _n("reality"), translate("REALITY"))
o.default = 0 o.default = 0
o:depends({ [_n("protocol")] = "http", [_n("tls")] = true }) o:depends({ [_n("protocol")] = "http", [_n("tls")] = true })
@@ -264,7 +264,7 @@ if singbox_tags:find("with_utls") then
o:depends({ [_n("reality")] = true }) o:depends({ [_n("reality")] = true })
end end
-- [[ TLS部分 ]] -- -- [[ TLS ]] --
o = s:option(FileUpload, _n("tls_certificateFile"), translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem") o = s:option(FileUpload, _n("tls_certificateFile"), translate("Public key absolute path"), translate("as:") .. "/etc/ssl/fullchain.pem")
o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. arg[1] .. ".pem" o.default = m:get(s.section, "tls_certificateFile") or "/etc/config/ssl/" .. arg[1] .. ".pem"
@@ -346,7 +346,7 @@ o:depends({ [_n("protocol")] = "vmess" })
o:depends({ [_n("protocol")] = "vless" }) o:depends({ [_n("protocol")] = "vless" })
o:depends({ [_n("protocol")] = "trojan" }) o:depends({ [_n("protocol")] = "trojan" })
-- [[ HTTP部分 ]]-- -- [[ HTTP ]]--
o = s:option(DynamicList, _n("http_host"), translate("HTTP Host")) o = s:option(DynamicList, _n("http_host"), translate("HTTP Host"))
o:depends({ [_n("transport")] = "http" }) o:depends({ [_n("transport")] = "http" })
@@ -354,7 +354,7 @@ o:depends({ [_n("transport")] = "http" })
o = s:option(Value, _n("http_path"), translate("HTTP Path")) o = s:option(Value, _n("http_path"), translate("HTTP Path"))
o:depends({ [_n("transport")] = "http" }) o:depends({ [_n("transport")] = "http" })
-- [[ WebSocket部分 ]]-- -- [[ WebSocket ]]--
o = s:option(Value, _n("ws_host"), translate("WebSocket Host")) o = s:option(Value, _n("ws_host"), translate("WebSocket Host"))
o:depends({ [_n("transport")] = "ws" }) o:depends({ [_n("transport")] = "ws" })
@@ -362,7 +362,7 @@ o:depends({ [_n("transport")] = "ws" })
o = s:option(Value, _n("ws_path"), translate("WebSocket Path")) o = s:option(Value, _n("ws_path"), translate("WebSocket Path"))
o:depends({ [_n("transport")] = "ws" }) o:depends({ [_n("transport")] = "ws" })
-- [[ HTTPUpgrade部分 ]]-- -- [[ HTTPUpgrade ]]--
o = s:option(Value, _n("httpupgrade_host"), translate("HTTPUpgrade Host")) o = s:option(Value, _n("httpupgrade_host"), translate("HTTPUpgrade Host"))
o:depends({ [_n("transport")] = "httpupgrade" }) o:depends({ [_n("transport")] = "httpupgrade" })
@@ -370,7 +370,7 @@ o:depends({ [_n("transport")] = "httpupgrade" })
o = s:option(Value, _n("httpupgrade_path"), translate("HTTPUpgrade Path")) o = s:option(Value, _n("httpupgrade_path"), translate("HTTPUpgrade Path"))
o:depends({ [_n("transport")] = "httpupgrade" }) o:depends({ [_n("transport")] = "httpupgrade" })
-- [[ gRPC部分 ]]-- -- [[ gRPC ]]--
o = s:option(Value, _n("grpc_serviceName"), "ServiceName") o = s:option(Value, _n("grpc_serviceName"), "ServiceName")
o:depends({ [_n("transport")] = "grpc" }) o:depends({ [_n("transport")] = "grpc" })

View File

@@ -8,6 +8,11 @@ util = require "luci.util"
datatypes = require "luci.cbi.datatypes" datatypes = require "luci.cbi.datatypes"
jsonc = require "luci.jsonc" jsonc = require "luci.jsonc"
i18n = require "luci.i18n" i18n = require "luci.i18n"
local lang = uci:get("luci", "main", "lang") or "auto"
if lang == "auto" then
lang = i18n.default
end
i18n.setlanguage(lang)
appname = "passwall2" appname = "passwall2"
curl_args = { "-skfL", "--connect-timeout 3", "--retry 3" } curl_args = { "-skfL", "--connect-timeout 3", "--retry 3" }
@@ -128,7 +133,7 @@ function base64Encode(text)
return result return result
end end
--提取URL中的域名和端口(no ip) -- Extract the domain name and port from the URL (no IP address).
function get_domain_port_from_url(url) function get_domain_port_from_url(url)
local scheme, domain, port = string.match(url, "^(https?)://([%w%.%-]+):?(%d*)") local scheme, domain, port = string.match(url, "^(https?)://([%w%.%-]+):?(%d*)")
if not domain then if not domain then
@@ -141,7 +146,7 @@ function get_domain_port_from_url(url)
return domain, port return domain, port
end end
--解析域名 -- Domain resolution
function domainToIPv4(domain, dns) function domainToIPv4(domain, dns)
local Dns = dns or "223.5.5.5" local Dns = dns or "223.5.5.5"
local IPs = luci.sys.exec('nslookup %s %s | awk \'/^Name:/{getline; if ($1 == "Address:") print $2}\'' % { domain, Dns }) local IPs = luci.sys.exec('nslookup %s %s | awk \'/^Name:/{getline; if ($1 == "Address:") print $2}\'' % { domain, Dns })
@@ -161,7 +166,7 @@ function curl_base(url, file, args)
end end
function curl_proxy(url, file, args) function curl_proxy(url, file, args)
--使用代理 -- Use the proxy
local socks_server = get_cache_var("GLOBAL_SOCKS_server") local socks_server = get_cache_var("GLOBAL_SOCKS_server")
if socks_server and socks_server ~= "" then if socks_server and socks_server ~= "" then
if not args then args = {} end if not args then args = {} end
@@ -181,7 +186,7 @@ function curl_logic(url, file, args)
end end
function curl_direct(url, file, args) function curl_direct(url, file, args)
--直连访问 -- Direct access
if not args then args = {} end if not args then args = {} end
local tmp_args = clone(args) local tmp_args = clone(args)
local domain, port = get_domain_port_from_url(url) local domain, port = get_domain_port_from_url(url)
@@ -223,16 +228,15 @@ function trim(text)
return text:match("^%s*(.-)%s*$") return text:match("^%s*(.-)%s*$")
end end
-- 分割字符串
function split(full, sep) function split(full, sep)
if full then if full then
full = full:gsub("%z", "") -- 这里不是很清楚 有时候结尾带个\0 full = full:gsub("%z", "") -- This is not very clear; sometimes it ends with a `\0`.
local off, result = 1, {} local off, result = 1, {}
while true do while true do
local nStart, nEnd = full:find(sep, off) local nStart, nEnd = full:find(sep, off)
if not nEnd then if not nEnd then
local res = string.sub(full, off, string.len(full)) local res = string.sub(full, off, string.len(full))
if #res > 0 then -- 过滤掉 \0 if #res > 0 then -- Filter out `\0`
table.insert(result, res) table.insert(result, res)
end end
break break
@@ -1145,7 +1149,7 @@ end
function to_check_self() function to_check_self()
local url = "https://raw.githubusercontent.com/xiaorouji/openwrt-passwall2/main/luci-app-passwall2/Makefile" local url = "https://raw.githubusercontent.com/xiaorouji/openwrt-passwall2/main/luci-app-passwall2/Makefile"
local tmp_file = "/tmp/passwall2_makefile" local tmp_file = "/tmp/passwall2_makefile"
local return_code, result = curl_logic(url, tmp_file, curl_args) local return_code, result = curl_auto(url, tmp_file, curl_args)
result = return_code == 0 result = return_code == 0
if not result then if not result then
exec("/bin/rm", {"-f", tmp_file}) exec("/bin/rm", {"-f", tmp_file})
@@ -1155,8 +1159,8 @@ function to_check_self()
} }
end end
local local_version = get_version() local local_version = get_version()
local remote_version = sys.exec("echo -n $(grep 'PKG_VERSION' /tmp/passwall2_makefile|awk -F '=' '{print $2}')") local remote_version = sys.exec("echo -n $(grep '^PKG_VERSION' /tmp/passwall2_makefile | head -n 1 | awk -F '=' '{print $2}')")
.. "-" .. sys.exec("echo -n $(grep 'PKG_RELEASE' /tmp/passwall2_makefile|awk -F '=' '{print $2}')") exec("/bin/rm", {"-f", tmp_file})
local has_update = compare_versions(local_version, "<", remote_version) local has_update = compare_versions(local_version, "<", remote_version)
if not has_update then if not has_update then
@@ -1224,7 +1228,7 @@ function set_apply_on_parse(map)
end end
end end
map.render = function(self, ...) map.render = function(self, ...)
getmetatable(self).__index.render(self, ...) -- 保持原渲染流程 getmetatable(self).__index.render(self, ...) -- Maintain the original rendering process
optimize_cbi_ui() optimize_cbi_ui()
end end
end end
@@ -1243,7 +1247,7 @@ function luci_types(id, m, s, type_name, option_prefix)
end end
s.fields[key].cfgvalue = function(self, section) s.fields[key].cfgvalue = function(self, section)
-- 添加自定义 custom_cfgvalue 属性,如果有自定义的 custom_cfgvalue 函数,则使用自定义的 cfgvalue 逻辑 -- Add a custom `custom_cfgvalue` attribute. If a custom `custom_cfgvalue` function exists, the custom `cfgvalue` logic will be used.
if self.custom_cfgvalue then if self.custom_cfgvalue then
return self:custom_cfgvalue(section) return self:custom_cfgvalue(section)
else else
@@ -1258,7 +1262,7 @@ function luci_types(id, m, s, type_name, option_prefix)
end end
s.fields[key].write = function(self, section, value) s.fields[key].write = function(self, section, value)
if s.fields["type"]:formvalue(id) == type_name then if s.fields["type"]:formvalue(id) == type_name then
-- 添加自定义 custom_write 属性,如果有自定义的 custom_write 函数,则使用自定义的 write 逻辑 -- Add a custom `custom_write` attribute; if a custom `custom_write` function exists, then use the custom write logic.
if self.custom_write then if self.custom_write then
self:custom_write(section, value) self:custom_write(section, value)
else else
@@ -1274,7 +1278,7 @@ function luci_types(id, m, s, type_name, option_prefix)
end end
s.fields[key].remove = function(self, section) s.fields[key].remove = function(self, section)
if s.fields["type"]:formvalue(id) == type_name then if s.fields["type"]:formvalue(id) == type_name then
-- 添加自定义 custom_remove 属性,如果有自定义的 custom_remove 函数,则使用自定义的 remove 逻辑 -- Add a custom `custom_remove` attribute; if a custom `custom_remove` function exists, use the custom remove logic.
if self.custom_remove then if self.custom_remove then
self:custom_remove(section) self:custom_remove(section)
else else
@@ -1334,14 +1338,14 @@ end
function optimize_cbi_ui() function optimize_cbi_ui()
luci.http.write([[ luci.http.write([[
<script type="text/javascript"> <script type="text/javascript">
//修正上移、下移按钮名称 //Correct the names of the move up and move down buttons.
document.querySelectorAll("input.btn.cbi-button.cbi-button-up").forEach(function(btn) { document.querySelectorAll("input.btn.cbi-button.cbi-button-up").forEach(function(btn) {
btn.value = "]] .. i18n.translate("Move up") .. [["; btn.value = "]] .. i18n.translate("Move up") .. [[";
}); });
document.querySelectorAll("input.btn.cbi-button.cbi-button-down").forEach(function(btn) { document.querySelectorAll("input.btn.cbi-button.cbi-button-down").forEach(function(btn) {
btn.value = "]] .. i18n.translate("Move down") .. [["; btn.value = "]] .. i18n.translate("Move down") .. [[";
}); });
//删除控件和说明之间的多余换行 //Remove extra line breaks between controls and descriptions.
document.querySelectorAll("div.cbi-value-description").forEach(function(descDiv) { document.querySelectorAll("div.cbi-value-description").forEach(function(descDiv) {
var prev = descDiv.previousSibling; var prev = descDiv.previousSibling;
while (prev && prev.nodeType === Node.TEXT_NODE && prev.textContent.trim() === "") { while (prev && prev.nodeType === Node.TEXT_NODE && prev.textContent.trim() === "") {

View File

@@ -184,7 +184,7 @@ local function start()
f:write(jsonc.stringify(config, 1)) f:write(jsonc.stringify(config, 1))
f:close() f:close()
end end
log(string.format("%s 生成配置文件并运行 - %s", remarks, config_file)) log(string.format("%s %s - %s", remarks, api.i18n.translate("Generate configuration file and run"), config_file))
end end
if bin then if bin then

View File

@@ -33,7 +33,7 @@ end
function gen_config(var) function gen_config(var)
local node_id = var["-node"] local node_id = var["-node"]
if not node_id then if not node_id then
print("-node 不能为空") print("-node Cannot be empty!")
return return
end end
local node = uci:get_all("passwall2", node_id) local node = uci:get_all("passwall2", node_id)

View File

@@ -6,7 +6,7 @@ local jsonc = api.jsonc
function gen_config(var) function gen_config(var)
local node_id = var["-node"] local node_id = var["-node"]
if not node_id then if not node_id then
print("-node 不能为空") print("-node Cannot be empty!")
return return
end end
local node = uci:get_all("passwall2", node_id) local node = uci:get_all("passwall2", node_id)

View File

@@ -33,7 +33,7 @@ local plugin_sh, plugin_bin
function gen_config(var) function gen_config(var)
local node_id = var["-node"] local node_id = var["-node"]
if not node_id then if not node_id then
print("-node 不能为空") print("-node Cannot be empty!")
return return
end end
local node = uci:get_all("passwall2", node_id) local node = uci:get_all("passwall2", node_id)

View File

@@ -97,11 +97,10 @@ function gen_outbound(flag, node, tag, proxy_table)
end end
tls = { tls = {
enabled = true, enabled = true,
disable_sni = (node.tls_disable_sni == "1") and true or false, --不要在 ClientHello 中发送服务器名称. disable_sni = (node.tls_disable_sni == "1") and true or false, -- Do not send the server name in ClientHello.
server_name = node.tls_serverName, --用于验证返回证书上的主机名,除非设置不安全。它还包含在 ClientHello 中以支持虚拟主机,除非它是 IP 地址。 server_name = node.tls_serverName, -- Used to verify the hostname on the returned certificate, unless the settings are insecure. It is also included in ClientHello to support virtual hosts, unless it is an IP address.
insecure = (node.tls_allowInsecure == "1") and true or false, --接受任何服务器证书。 insecure = (node.tls_allowInsecure == "1") and true or false, -- Accepts any server certificate.
alpn = alpn, --支持的应用层协议协商列表,按优先顺序排列。如果两个对等点都支持 ALPN则选择的协议将是此列表中的一个如果没有相互支持的协议则连接将失败。 alpn = alpn, -- A list of supported application layer protocols, arranged in order of priority. If both peers support ALPN, the protocol selected will be one of these protocols; otherwise, the connection will fail.
--min_version = "1.2",
--max_version = "1.3", --max_version = "1.3",
fragment = fragment, fragment = fragment,
record_fragment = record_fragment, record_fragment = record_fragment,
@@ -142,7 +141,7 @@ function gen_outbound(flag, node, tag, proxy_table)
local v2ray_transport = nil local v2ray_transport = nil
if node.transport == "tcp" and node.tcp_guise == "http" and (node.tcp_guise_http_host or "") ~= "" then --模拟xray raw(tcp)传输 if node.transport == "tcp" and node.tcp_guise == "http" and (node.tcp_guise_http_host or "") ~= "" then -- Simulate X-ray Raw (TCP) transmission
v2ray_transport = { v2ray_transport = {
type = "http", type = "http",
host = node.tcp_guise_http_host, host = node.tcp_guise_http_host,
@@ -153,7 +152,7 @@ function gen_outbound(flag, node, tag, proxy_table)
idle_timeout = (node.http_h2_health_check == "1") and node.http_h2_read_idle_timeout or nil, idle_timeout = (node.http_h2_health_check == "1") and node.http_h2_read_idle_timeout or nil,
ping_timeout = (node.http_h2_health_check == "1") and node.http_h2_health_check_timeout or nil, ping_timeout = (node.http_h2_health_check == "1") and node.http_h2_health_check_timeout or nil,
} }
--不强制执行 TLS。如果未配置 TLS将使用纯 HTTP 1.1。 -- TLS is not enforced. If TLS is not configured, plain HTTP 1.1 will be used.
end end
if node.transport == "http" then if node.transport == "http" then
@@ -164,7 +163,7 @@ function gen_outbound(flag, node, tag, proxy_table)
idle_timeout = (node.http_h2_health_check == "1") and node.http_h2_read_idle_timeout or nil, idle_timeout = (node.http_h2_health_check == "1") and node.http_h2_read_idle_timeout or nil,
ping_timeout = (node.http_h2_health_check == "1") and node.http_h2_health_check_timeout or nil, ping_timeout = (node.http_h2_health_check == "1") and node.http_h2_health_check_timeout or nil,
} }
--不强制执行 TLS。如果未配置 TLS将使用纯 HTTP 1.1。 -- TLS is not enforced. If TLS is not configured, plain HTTP 1.1 will be used.
end end
if node.transport == "ws" then if node.transport == "ws" then
@@ -173,7 +172,7 @@ function gen_outbound(flag, node, tag, proxy_table)
path = node.ws_path or "/", path = node.ws_path or "/",
headers = (node.ws_host ~= nil) and { Host = node.ws_host } or nil, headers = (node.ws_host ~= nil) and { Host = node.ws_host } or nil,
max_early_data = tonumber(node.ws_maxEarlyData) or nil, max_early_data = tonumber(node.ws_maxEarlyData) or nil,
early_data_header_name = (node.ws_earlyDataHeaderName) and node.ws_earlyDataHeaderName or nil --要与 Xray-core 兼容,请将其设置为 Sec-WebSocket-Protocol。它需要与服务器保持一致。 early_data_header_name = (node.ws_earlyDataHeaderName) and node.ws_earlyDataHeaderName or nil -- For compatibility with Xray-core, set it to Sec-WebSocket-Protocol. It needs to be consistent with the server.
} }
end end
@@ -189,7 +188,7 @@ function gen_outbound(flag, node, tag, proxy_table)
v2ray_transport = { v2ray_transport = {
type = "quic" type = "quic"
} }
--没有额外的加密支持: 它基本上是重复加密。 并且 Xray-core 在这里与 v2ray-core 不兼容。 -- There is no additional encryption support: it's essentially re-encryption. And Xray-core is incompatible with v2ray-core here.
end end
if node.transport == "grpc" then if node.transport == "grpc" then
@@ -268,7 +267,7 @@ function gen_outbound(flag, node, tag, proxy_table)
global_padding = (node.global_padding == "1") and true or false, global_padding = (node.global_padding == "1") and true or false,
authenticated_length = (node.authenticated_length == "1") and true or false, authenticated_length = (node.authenticated_length == "1") and true or false,
tls = tls, tls = tls,
packet_encoding = "", --UDP 包编码。(空):禁用 packetaddr由 v2ray 5+ 支持 xudp xray 支持 packet_encoding = "", -- UDP packet encoding. (Empty): Disabled. packetaddr: Supported by v2ray 5+. xudp: Supported by xray.
multiplex = mux, multiplex = mux,
transport = v2ray_transport, transport = v2ray_transport,
} }
@@ -279,7 +278,7 @@ function gen_outbound(flag, node, tag, proxy_table)
uuid = node.uuid, uuid = node.uuid,
flow = (node.tls == '1' and node.flow) and node.flow or nil, flow = (node.tls == '1' and node.flow) and node.flow or nil,
tls = tls, tls = tls,
packet_encoding = "xudp", --UDP 包编码。(空):禁用 packetaddr由 v2ray 5+ 支持 xudp xray 支持 packet_encoding = "xudp", -- UDP packet encoding. (Empty): Disabled. packetaddr: Supported by v2ray 5+. xudp: Supported by xray.
multiplex = mux, multiplex = mux,
transport = v2ray_transport, transport = v2ray_transport,
} }
@@ -529,7 +528,7 @@ function gen_config_server(node)
type = "ws", type = "ws",
path = node.ws_path or "/", path = node.ws_path or "/",
headers = (node.ws_host ~= nil) and { Host = node.ws_host } or nil, headers = (node.ws_host ~= nil) and { Host = node.ws_host } or nil,
early_data_header_name = (node.ws_earlyDataHeaderName) and node.ws_earlyDataHeaderName or nil --要与 Xray-core 兼容,请将其设置为 Sec-WebSocket-Protocol。它需要与服务器保持一致。 early_data_header_name = (node.ws_earlyDataHeaderName) and node.ws_earlyDataHeaderName or nil -- For compatibility with Xray-core, set it to Sec-WebSocket-Protocol. It needs to be consistent with the server.
} }
end end
@@ -545,7 +544,7 @@ function gen_config_server(node)
v2ray_transport = { v2ray_transport = {
type = "quic" type = "quic"
} }
--没有额外的加密支持: 它基本上是重复加密。 并且 Xray-core 在这里与 v2ray-core 不兼容。 -- There is no additional encryption support: it's essentially re-encryption. And Xray-core is incompatible with v2ray-core here.
end end
if node.transport == "grpc" then if node.transport == "grpc" then
@@ -1474,9 +1473,9 @@ function gen_config(var)
servers = {}, servers = {},
rules = {}, rules = {},
disable_cache = (dns_cache and dns_cache == "0") and true or false, disable_cache = (dns_cache and dns_cache == "0") and true or false,
disable_expire = false, --禁用 DNS 缓存过期。 disable_expire = false, -- Disable DNS cache expiration.
independent_cache = false, --使每个 DNS 服务器的缓存独立,以满足特殊目的。如果启用,将轻微降低性能。 independent_cache = false, -- Make each DNS server's cache independent for specific purposes. If enabled, it will slightly reduce performance.
reverse_mapping = true, --在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。 reverse_mapping = true, -- After responding to a DNS query, a reverse mapping of the IP address is stored to provide the domain name for routing purposes.
fakeip = nil, fakeip = nil,
} }
@@ -1590,7 +1589,7 @@ function gen_config(var)
end end
dns.final = default_dns_flag dns.final = default_dns_flag
--按分流顺序DNS -- DNS in order of shunt
if dns_domain_rules and #dns_domain_rules > 0 then if dns_domain_rules and #dns_domain_rules > 0 then
for index, value in ipairs(dns_domain_rules) do for index, value in ipairs(dns_domain_rules) do
if value.outboundTag and (value.domain or value.domain_suffix or value.domain_keyword or value.domain_regex or value.geosite or value.rule_set) then if value.outboundTag and (value.domain or value.domain_suffix or value.domain_keyword or value.domain_regex or value.geosite or value.rule_set) then
@@ -1711,15 +1710,10 @@ function gen_config(var)
timestamp = true, timestamp = true,
output = logfile, output = logfile,
}, },
-- DNS
dns = dns, dns = dns,
-- 传入连接
inbounds = inbounds, inbounds = inbounds,
-- 传出连接
outbounds = outbounds, outbounds = outbounds,
-- 路由
route = route, route = route,
--实验性
experimental = experimental, experimental = experimental,
} }
table.insert(outbounds, { table.insert(outbounds, {
@@ -1919,9 +1913,7 @@ function gen_proto_config(var)
level = "warn", level = "warn",
timestamp = true, timestamp = true,
}, },
-- 传入连接
inbounds = inbounds, inbounds = inbounds,
-- 传出连接
outbounds = outbounds, outbounds = outbounds,
} }
return jsonc.stringify(config, 1) return jsonc.stringify(config, 1)

View File

@@ -6,7 +6,7 @@ local json = api.jsonc
function gen_config(var) function gen_config(var)
local node_id = var["-node"] local node_id = var["-node"]
if not node_id then if not node_id then
print("-node 不能为空") print("-node Cannot be empty!")
return return
end end
local node = uci:get_all("passwall2", node_id) local node = uci:get_all("passwall2", node_id)

View File

@@ -139,7 +139,6 @@ function gen_outbound(flag, node, tag, proxy_table)
concurrency = (node.mux == "1" and ((node.mux_concurrency) and tonumber(node.mux_concurrency) or -1)) or nil, concurrency = (node.mux == "1" and ((node.mux_concurrency) and tonumber(node.mux_concurrency) or -1)) or nil,
xudpConcurrency = (node.mux == "1" and ((node.xudp_concurrency) and tonumber(node.xudp_concurrency) or 8)) or nil xudpConcurrency = (node.mux == "1" and ((node.xudp_concurrency) and tonumber(node.xudp_concurrency) or 8)) or nil
} or nil, } or nil,
-- 底层传输配置
streamSettings = (node.streamSettings or node.protocol == "vmess" or node.protocol == "vless" or node.protocol == "socks" or node.protocol == "shadowsocks" or node.protocol == "trojan") and { streamSettings = (node.streamSettings or node.protocol == "vmess" or node.protocol == "vless" or node.protocol == "socks" or node.protocol == "shadowsocks" or node.protocol == "trojan") and {
sockopt = { sockopt = {
mark = 255, mark = 255,
@@ -217,7 +216,7 @@ function gen_outbound(flag, node, tag, proxy_table)
mode = node.xhttp_mode or "auto", mode = node.xhttp_mode or "auto",
path = node.xhttp_path or "/", path = node.xhttp_path or "/",
host = node.xhttp_host, host = node.xhttp_host,
-- 如果包含 "extra" 节,取 "extra" 内的内容,否则直接赋值给 extra -- If the code contains an "extra" section, retrieve the contents of "extra"; otherwise, assign the value directly to "extra".
extra = node.xhttp_extra and (function() extra = node.xhttp_extra and (function()
local success, parsed = pcall(jsonc.parse, node.xhttp_extra) local success, parsed = pcall(jsonc.parse, node.xhttp_extra)
if success then if success then
@@ -445,7 +444,6 @@ function gen_config_server(node)
log = { log = {
loglevel = ("1" == node.log) and node.loglevel or "none" loglevel = ("1" == node.log) and node.loglevel or "none"
}, },
-- 传入连接
inbounds = { inbounds = {
{ {
listen = (node.bind_local == "1") and "127.0.0.1" or nil, listen = (node.bind_local == "1") and "127.0.0.1" or nil,
@@ -519,7 +517,6 @@ function gen_config_server(node)
} }
} }
}, },
-- 传出连接
outbounds = outbounds, outbounds = outbounds,
routing = routing routing = routing
} }
@@ -1523,18 +1520,12 @@ function gen_config(var)
--dnsLog = true, --dnsLog = true,
loglevel = loglevel loglevel = loglevel
}, },
-- DNS
dns = dns, dns = dns,
fakedns = fakedns, fakedns = fakedns,
-- 传入连接
inbounds = inbounds, inbounds = inbounds,
-- 传出连接
outbounds = outbounds, outbounds = outbounds,
-- 连接观测
burstObservatory = burstObservatory, burstObservatory = burstObservatory,
-- 路由
routing = routing, routing = routing,
-- 本地策略
policy = { policy = {
levels = { levels = {
[0] = { [0] = {
@@ -1704,7 +1695,6 @@ function gen_proto_config(var)
if outbound then table.insert(outbounds, outbound) end if outbound then table.insert(outbounds, outbound) end
end end
-- 额外传出连接
table.insert(outbounds, { table.insert(outbounds, {
protocol = "freedom", tag = "direct", settings = {keep = ""} protocol = "freedom", tag = "direct", settings = {keep = ""}
}) })
@@ -1713,11 +1703,8 @@ function gen_proto_config(var)
log = { log = {
loglevel = "warning" loglevel = "warning"
}, },
-- 传入连接
inbounds = inbounds, inbounds = inbounds,
-- 传出连接
outbounds = outbounds, outbounds = outbounds,
-- 路由
routing = routing routing = routing
} }
return jsonc.stringify(config, 1) return jsonc.stringify(config, 1)
@@ -1954,13 +1941,9 @@ function gen_dns_config(var)
--dnsLog = true, --dnsLog = true,
loglevel = loglevel loglevel = loglevel
}, },
-- DNS
dns = dns, dns = dns,
-- 传入连接
inbounds = inbounds, inbounds = inbounds,
-- 传出连接
outbounds = outbounds, outbounds = outbounds,
-- 路由
routing = routing routing = routing
} }
return jsonc.stringify(config, 1) return jsonc.stringify(config, 1)

View File

@@ -106,7 +106,7 @@ local api = require "luci.passwall2.api"
}) })
.then(response => { .then(response => {
if (!response.ok) { if (!response.ok) {
throw new Error("备份失败!"); throw new Error("<%:Backup failed!%>");
} }
const filename = response.headers.get("X-Backup-Filename"); const filename = response.headers.get("X-Backup-Filename");
if (!filename) { if (!filename) {
@@ -175,7 +175,7 @@ local api = require "luci.passwall2.api"
const base64Data = btoa(binaryText); const base64Data = btoa(binaryText);
const targetByteSize = 64 * 1024; // 分片大小 64KB const targetByteSize = 64 * 1024; // Slice Size 64KB
let chunkSize = Math.floor(targetByteSize * 4 / 3); let chunkSize = Math.floor(targetByteSize * 4 / 3);
chunkSize = chunkSize + (4 - (chunkSize % 4)) % 4; chunkSize = chunkSize + (4 - (chunkSize % 4)) % 4;
const totalChunks = Math.ceil(base64Data.length / chunkSize); const totalChunks = Math.ceil(base64Data.length / chunkSize);

View File

@@ -5,7 +5,7 @@ local api = require "luci.passwall2.api"
<script type="text/javascript"> <script type="text/javascript">
//<![CDATA[ //<![CDATA[
function ajax_add_node(link, group) { function ajax_add_node(link, group) {
const chunkSize = 1000; // 分片发送以突破uhttpd的限制每块1000字符 const chunkSize = 1000; // Fragmented sending to overcome uhttpd's limitations, with each fragment containing 1000 characters.
const totalChunks = Math.ceil(link.length / chunkSize); const totalChunks = Math.ceil(link.length / chunkSize);
let currentChunk = 0; let currentChunk = 0;
@@ -86,7 +86,7 @@ local api = require "luci.passwall2.api"
} }
//自定义分组下拉列表事件 // Custom group dropdown list events
document.addEventListener("DOMContentLoaded", function() { document.addEventListener("DOMContentLoaded", function() {
var dropdown = document.getElementById("addlink_group_custom"); var dropdown = document.getElementById("addlink_group_custom");
if (!dropdown) return; if (!dropdown) return;
@@ -127,8 +127,16 @@ local api = require "luci.passwall2.api"
var val = input.value.trim(); var val = input.value.trim();
if (!val) return; if (!val) return;
if (val.toLowerCase() === "default") {
var emptyLi = Array.from(list.querySelectorAll(".dropdown-item"))
.find(function(el){ return !el.dataset.value; });
if (emptyLi) selectItem(emptyLi);
input.value = "";
return;
}
var li = Array.from(list.querySelectorAll(".dropdown-item")).find(function(el){ var li = Array.from(list.querySelectorAll(".dropdown-item")).find(function(el){
return el.dataset.value === val; return el.dataset.value.toLowerCase() === val.toLowerCase();
}); });
if (!li) { if (!li) {
li = document.createElement("li"); li = document.createElement("li");
@@ -141,7 +149,7 @@ local api = require "luci.passwall2.api"
input.value = ""; input.value = "";
selectItem(li); selectItem(li);
}); });
// 从tab中读取分组名称 // Read group names from tab
var observer = new MutationObserver(function(mutations, obs){ var observer = new MutationObserver(function(mutations, obs){
var tabs = document.querySelectorAll(".cbi-tabmenu li"); var tabs = document.querySelectorAll(".cbi-tabmenu li");
if(!tabs.length) return; if(!tabs.length) return;
@@ -163,7 +171,7 @@ local api = require "luci.passwall2.api"
}); });
observer.observe(document.body, {childList: true, subtree: true}); observer.observe(document.body, {childList: true, subtree: true});
// 点击外部时自动收起 // Automatically collapses when clicking on the outside.
document.addEventListener("click", function(e) { document.addEventListener("click", function(e) {
if (!dropdown.contains(e.target)) { if (!dropdown.contains(e.target)) {
list.style.display = "none"; list.style.display = "none";

View File

@@ -215,7 +215,7 @@ local hysteria2_type = get_core("hysteria2_type", {{has_hysteria2,"hysteria2"},{
v_port.value + "/?"; v_port.value + "/?";
var shadow_tls; var shadow_tls;
//生成SS Shadow-TLS 插件参数 // Generate SS Shadow-TLS plugin parameters
const generateShadowTLSBase64 = function(paramStr) { const generateShadowTLSBase64 = function(paramStr) {
try { try {
let obj = {}; let obj = {};
@@ -746,13 +746,13 @@ local hysteria2_type = get_core("hysteria2_type", {{has_hysteria2,"hysteria2"},{
} catch (err) {} } catch (err) {}
} else { } else {
//alert('<%:Faltal on set option, please help in debug: %>' + opt + ' = ' + val); //alert('<%:Faltal on set option, please help in debug: %>' + opt + ' = ' + val);
// 处理 DynamicList // Processing DynamicList
var fullName = this.base + '.' + opt; var fullName = this.base + '.' + opt;
var lists = document.querySelectorAll('.cbi-dynlist'); var lists = document.querySelectorAll('.cbi-dynlist');
for (var i = 0; i < lists.length; i++) { for (var i = 0; i < lists.length; i++) {
var parent = lists[i].closest('.cbi-value'); var parent = lists[i].closest('.cbi-value');
if (!parent) continue; if (!parent) continue;
// 尝试从 label for 属性中提取 fullName // Try to extract fullName from the label's for attribute.
var label = parent.querySelector('label.cbi-value-title'); var label = parent.querySelector('label.cbi-value-title');
var labelFor = label?.getAttribute('for'); var labelFor = label?.getAttribute('for');
if (labelFor === fullName) { if (labelFor === fullName) {
@@ -793,7 +793,7 @@ local hysteria2_type = get_core("hysteria2_type", {{has_hysteria2,"hysteria2"},{
if (ssrurl === null || ssrurl === "") { if (ssrurl === null || ssrurl === "") {
return false; return false;
} }
ssrurl = ssrurl.replace(/&amp;/gi, '&').replace(/\s*#\s*/, '#').trim(); //一些奇葩的链接用"&amp;"当做"&""#"前后带空格 ssrurl = ssrurl.replace(/&amp;/gi, '&').replace(/\s*#\s*/, '#').trim(); // Some odd links use "&" as "&", and include spaces before and after "#".
s.innerHTML = ""; s.innerHTML = "";
var ssu = ssrurl.split('://'); var ssu = ssrurl.split('://');
var event = document.createEvent("HTMLEvents"); var event = document.createEvent("HTMLEvents");
@@ -872,7 +872,7 @@ local hysteria2_type = get_core("hysteria2_type", {{has_hysteria2,"hysteria2"},{
method = userInfo.substr(0, userInfoSplitIndex); method = userInfo.substr(0, userInfoSplitIndex);
password = userInfo.substr(userInfoSplitIndex + 1); password = userInfo.substr(userInfoSplitIndex + 1);
} else { } else {
password = url0.substr(0, sipIndex); //一些链接用明文uuid做密码 password = url0.substr(0, sipIndex); // Some links use plaintext UUIDs as passwords.
} }
} else { } else {
// base64(method:pass@host:port) // base64(method:pass@host:port)
@@ -892,7 +892,7 @@ local hysteria2_type = get_core("hysteria2_type", {{has_hysteria2,"hysteria2"},{
} }
} }
// 判断密码是否经过url编码 // Determine if the password is URL encoded
const isURLEncodedPassword = function(pwd) { const isURLEncodedPassword = function(pwd) {
if (!/%[0-9A-Fa-f]{2}/.test(pwd)) return false; if (!/%[0-9A-Fa-f]{2}/.test(pwd)) return false;
try { try {
@@ -949,7 +949,7 @@ local hysteria2_type = get_core("hysteria2_type", {{has_hysteria2,"hysteria2"},{
opt.set(dom_prefix + 'plugin_enabled', true); opt.set(dom_prefix + 'plugin_enabled', true);
opt.set(dom_prefix + 'plugin', plugin || "none"); opt.set(dom_prefix + 'plugin', plugin || "none");
opt.set(dom_prefix + 'plugin_opts', pluginOpts || ""); opt.set(dom_prefix + 'plugin_opts', pluginOpts || "");
//obfs-local插件转换成xray支持的格式 // The obfs-local plugin converts data to a format supported by xray.
if (plugin == "obfs-local" && dom_prefix == "xray_") { if (plugin == "obfs-local" && dom_prefix == "xray_") {
var obfs = pluginOpts.match(/obfs=([^;]+)/); var obfs = pluginOpts.match(/obfs=([^;]+)/);
var obfs_host = pluginOpts.match(/obfs-host=([^;]+)/); var obfs_host = pluginOpts.match(/obfs-host=([^;]+)/);
@@ -1075,7 +1075,7 @@ local hysteria2_type = get_core("hysteria2_type", {{has_hysteria2,"hysteria2"},{
} }
if (queryParam["shadow-tls"]) { if (queryParam["shadow-tls"]) {
//解析SS Shadow-TLS 插件参数 // Parsing SS Shadow-TLS plugin parameters
const parseShadowTLSParams = function(base64Str, outObj) { const parseShadowTLSParams = function(base64Str, outObj) {
try { try {
let obj = JSON.parse(b64decsafe(base64Str)); let obj = JSON.parse(b64decsafe(base64Str));

View File

@@ -76,7 +76,7 @@ table td, .table .td {
function cbi_t_switch(section, tab) { function cbi_t_switch(section, tab) {
if( cbi_t[section] && cbi_t[section][tab] ) { if( cbi_t[section] && cbi_t[section][tab] ) {
//在切换选项卡之前,先取消当前激活选项卡的全选状态 // Before switching tabs, first deselect all currently active tabs.
var btn = document.getElementById("select_all_btn"); var btn = document.getElementById("select_all_btn");
if (btn) { if (btn) {
dechecked_all_node(btn); dechecked_all_node(btn);
@@ -646,7 +646,7 @@ table td, .table .td {
} }
tab_ul_html += tab_ul_li_html + '</ul>' tab_ul_html += tab_ul_li_html + '</ul>'
tab_content_html += tab_content_html + '</fieldset>' tab_content_html += '</fieldset>'
var tab_html = tab_ul_html + tab_content_html var tab_html = tab_ul_html + tab_content_html
document.getElementById("node_list").innerHTML = tab_html document.getElementById("node_list").innerHTML = tab_html

View File

@@ -26,7 +26,7 @@ local api = require "luci.passwall2.api"
var id = onclick_str.substring(onclick_str.lastIndexOf('/') + 1, onclick_str.length - 1); var id = onclick_str.substring(onclick_str.lastIndexOf('/') + 1, onclick_str.length - 1);
var td = edit_btn[i].parentNode; var td = edit_btn[i].parentNode;
var new_div = ""; var new_div = "";
//添加"日志"按钮 // Add `log` button
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;'; 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; td.innerHTML = new_div + td.innerHTML;
} }

View File

@@ -1,6 +1,3 @@
msgid "PassWall 2"
msgstr "PassWall 2"
msgid "Auto" msgid "Auto"
msgstr "自动" msgstr "自动"
@@ -979,6 +976,12 @@ msgstr "手动订阅全部"
msgid "This remark already exists, please change a new remark." msgid "This remark already exists, please change a new remark."
msgstr "此备注已存在,请改一个新的备注。" msgstr "此备注已存在,请改一个新的备注。"
msgid "Remark cannot be empty."
msgstr "备注不能为空。"
msgid "URL cannot be empty."
msgstr "网址不能为空。"
msgid "Filter keyword Mode" msgid "Filter keyword Mode"
msgstr "过滤关键字模式" msgstr "过滤关键字模式"
@@ -1702,6 +1705,9 @@ msgstr "下载备份"
msgid "RST Backup" msgid "RST Backup"
msgstr "恢复备份" msgstr "恢复备份"
msgid "Backup failed!"
msgstr "备份失败!"
msgid "UL Restore" msgid "UL Restore"
msgstr "上传恢复" msgstr "上传恢复"
@@ -1729,6 +1735,18 @@ msgstr "是否要恢复客户端默认配置?"
msgid "Are you sure you want to restore the client to default settings?" msgid "Are you sure you want to restore the client to default settings?"
msgstr "是否真的要恢复客户端默认配置?" msgstr "是否真的要恢复客户端默认配置?"
msgid "Configuration file uploaded successfully…"
msgstr "配置文件上传成功…"
msgid "Configuration restored successfully…"
msgstr "配置还原成功…"
msgid "Service restarting…"
msgstr "重启服务中…"
msgid "Configuration file decompression failed, please try again!"
msgstr "配置文件解压失败,请重试!"
msgid "_urltest" msgid "_urltest"
msgstr "URLTest" msgstr "URLTest"
@@ -1842,3 +1860,384 @@ msgstr "分组名"
msgid "Using..." msgid "Using..."
msgstr "使用中。" msgstr "使用中。"
msgid "Generate configuration file and run"
msgstr "生成配置文件并运行"
msgid "Start after a delay of %s seconds!"
msgstr "延时 %s 秒后再启动!"
msgid "The script is already running, do not run it again. Exit."
msgstr "脚本已经在运行,不重复运行,退出."
msgid "Stop the script and wait for a timeout, then exit without repeating the process."
msgstr "停止脚本等待超时,不重复运行,退出."
msgid "%s Transparent proxy base dependencies %s Not installed..."
msgstr "%s 透明代理基础依赖 %s 未安装..."
msgid "%s does not have execute permissions and cannot be started: %s %s"
msgstr "%s 没有执行权限,无法启动:%s %s"
msgid "%s not found, unable to start..."
msgstr "找不到 %s无法启动..."
msgid "Socks node: [%s]%s is an invalid server address and cannot be started!"
msgstr "Socks节点[%s]%s 是非法的服务器地址,无法启动!"
msgid "For some reason, the configuration for this Socks service has been lost, and its startup has been aborted!"
msgstr "某种原因,此 Socks 服务的相关配置已失联,启动中止!"
msgid "Socks node: [%s]%s, start failed %s:%s %s"
msgstr "Socks节点[%s]%s启动失败 %s:%s %s"
msgid "Socks node: [%s]%s, starting %s:%s"
msgstr "Socks节点[%s]%s启动 %s:%s"
msgid "To enable experimental IPv6 transparent proxy (TProxy), please ensure your node and type support IPv6!"
msgstr "开启实验性IPv6透明代理(TProxy)请确认您的节点及类型支持IPv6"
msgid "Analyzing the node configuration of the Socks service..."
msgstr "分析 Socks 服务的节点配置..."
msgid "Log file is too long, clear it!"
msgstr "日志文件过长,清空处理!"
msgid "The task is currently running automatically as a scheduled task; no reconfiguration of the scheduled task is required."
msgstr "当前为计划任务自动运行,不重新配置定时任务。"
msgid "Scheduled tasks: Auto stop service."
msgstr "配置定时任务:自动关闭服务。"
msgid "Scheduled tasks: Auto start service."
msgstr "配置定时任务:自动开启服务。"
msgid "Scheduled tasks: Auto restart service."
msgstr "配置定时任务:自动重启服务。"
msgid "Scheduled tasks: Auto update rules."
msgstr "配置定时任务:自动更新规则。"
msgid "Scheduled tasks: Auto update [%s] subscription."
msgstr "配置定时任务:自动更新 【%s】 订阅。"
msgid "Auto updates: Starts a cyclical update process."
msgstr "自动更新:启动循环更新进程。"
msgid "Running in no proxy mode, it only allows scheduled tasks for starting and stopping services."
msgstr "运行于非代理模式,仅允许服务启停的定时任务。"
msgid "Clear scheduled commands."
msgstr "清除定时执行命令。"
msgid "Unable to resolve [%s], route table addition failed!"
msgstr "无法解析[%s],路由表添加失败!"
msgid "[%s] was successfully added to the routing table of interface [%s]!"
msgstr "[%s]添加到接口[%s]路由表成功!"
msgid "Adding [%s] to the [%s] routing table failed! The reason is that the [%s] gateway cannot be found."
msgstr "[%s]添加到接口[%s]路由表失功!原因是找不到[%s]网关。"
msgid "Global nodes are not enabled, skip [%s]."
msgstr "全局节点未启用,跳过【%s】"
msgid "Direct DNS: %s"
msgstr "直连 DNS%s"
msgid "Remote DNS: %s"
msgstr "远程 DNS%s"
msgid "The program has started. Please stop it and then restart it!"
msgstr "程序已启动,先停止再重新启动!"
msgid "The system does not have iptables or ipset installed, or Dnsmasq does not have ipset support enabled, so iptables+ipset transparent proxy cannot be used!"
msgstr "系统未安装iptables或ipset或Dnsmasq没有开启ipset支持无法使用iptables+ipset透明代理"
msgid "fw4 detected, use nftables to transparent proxy."
msgstr "检测到fw4使用nftables进行透明代理。"
msgid "The Dnsmasq package does not meet the requirements for transparent proxy in nftables. If you need to use it, please ensure that the dnsmasq version is 2.87 or higher and that nftset support is enabled."
msgstr "Dnsmasq软件包不满足nftables透明代理要求如需使用请确保dnsmasq版本在2.87以上并开启nftset支持。"
msgid "If your Dnsmasq version is lower than 2.90, it is recommended to upgrade to version 2.90 or higher to avoid Dnsmasq crashing in some cases!"
msgstr "Dnsmasq版本低于2.90建议升级至2.90及以上版本以避免部分情况下Dnsmasq崩溃问题"
msgid "Running complete!"
msgstr "运行完成!"
msgid "Clearing and closing related programs and cache complete."
msgstr "清空并关闭相关程序和缓存完成。"
msgid "Incorrect index listing method (%s), execution terminated!"
msgstr "索引列举方式不正确(%s终止执行"
msgid "parse the traffic splitting rules[%s]-[geoip:%s] add to %s to complete."
msgstr "解析分流规则[%s]-[geoip:%s]加入到 %s 完成"
msgid "Access Control:"
msgstr "访问控制:"
msgid "Add node to the load balancer is directly connected to %s[%s]."
msgstr "加入负载均衡的节点到%s[%s]直连完成。"
msgid "Add all %s nodes to %s[%s] direct connection complete."
msgstr "加入所有%s节点到%s[%s]直连完成。"
msgid "Starting to load %s firewall rules..."
msgstr "开始加载 %s 防火墙规则..."
msgid "local network segments (%s) direct connection: %s"
msgstr "本机 %s 网段直连:%s"
msgid "Add ISP %s DNS to the whitelist: %s"
msgstr "追加ISP %s DNS 到白名单:%s"
msgid "[%s]"
msgstr "【%s】"
msgid "[%s],"
msgstr "【%s】"
msgid "Source iface [%s],"
msgstr "源接口【%s】"
msgid "IP range [%s],"
msgstr "IP 范围【%s】"
msgid "All device,"
msgstr "所有设备,"
msgid "Does not exist, ignore."
msgstr "不存在,忽略。"
msgid "not proxy %s port [%s]"
msgstr "不代理 %s 端口 [%s]"
msgid "not proxy all %s"
msgstr "不代理所有 %s"
msgid "Use the %s node [%s]"
msgstr "使用 %s 节点[%s]"
msgid "Using a node that is different from the global configuration, DNS has been forcibly redirected to a dedicated DNS server."
msgstr "使用与全局配置不相同节点已将DNS强制重定向到专用 DNS 服务器。"
msgid "Add direct DNS to %s: %s"
msgstr "追加直连 DNS 到 %s %s"
msgid "Local"
msgstr "本机"
msgid "[Local],"
msgstr "【本机】,"
msgid "Clear %s."
msgstr "清除 %s。"
msgid "Delete %s rules is complete."
msgstr "删除 %s 规则完成。"
msgid "%s firewall rules load complete!"
msgstr "%s 防火墙规则加载完成!"
msgid "Socks switch detection: Unknown error."
msgstr "Socks切换检测未知错误。"
msgid "Socks switch detection: Unable to connect to the network. Please check if the network is working properly!"
msgstr "Socks切换检测无法连接到网络请检查网络是否正常"
msgid "Socks switch detection: Primary node 【%s: [%s]】 is normal. Switch to the primary node!"
msgstr "Socks切换检测%s 主节点【%s[%s]】正常,切换到主节点!"
msgid "Socks switch detection: %s node switch complete!"
msgstr "Socks切换检测%s 节点切换完毕!"
msgid "Socks switch detection: %s 【%s:[%s]】 normal."
msgstr "Socks切换检测%s 【%s:[%s]】 正常。"
msgid "switch to %s test detect!"
msgstr "切换到 %s 检测!"
msgid "backup node"
msgstr "备用节点"
msgid "next backup node"
msgstr "下一个备用节点"
msgid "main node"
msgstr "主节点"
msgid "Socks switch detection: Unknown error."
msgstr "Socks切换检测未知错误。"
msgid "Socks switch detection: %s 【%s:[%s]】 abnormal, %s"
msgstr "Socks切换检测%s 【%s:[%s]】 异常,%s"
msgid "Socks switch detection: %s 【%s:[%s]】 normal, switch to this node!"
msgstr "Socks切换检测%s 【%s:[%s]】 正常,切换到此节点!"
msgid "Restart dnsmasq service."
msgstr "重启 dnsmasq 服务。"
msgid "Console Port: %s"
msgstr "控制台端口:%s"
msgid "Discard one obviously invalid node."
msgstr "丢弃 1 个明显无效的节点"
msgid "Entrance %s:%s"
msgstr "入口 %s:%s"
msgid "Node: %s:%s, Weight: %s"
msgstr "节点:%s:%s权重%s"
msgid "Start updating the rules..."
msgstr "开始更新规则..."
msgid "%s Start updating..."
msgstr "%s 开始更新..."
msgid "%s version is the same and does not need to be updated."
msgstr "%s 版本一致,无需更新。"
msgid "%s update success."
msgstr "%s 更新成功。"
msgid "%s update failed, please try again later."
msgstr "%s 更新失败,请稍后再试。"
msgid "Restart the service and apply the new rules."
msgstr "重启服务,应用新的规则。"
msgid "The rules have been updated..."
msgstr "规则更新完毕..."
msgid "Socks node list [%s]"
msgstr "Socks 节点列表 [%s]"
msgid "Backup node list"
msgstr "备用节点的列表"
msgid "HAProxy node list [%s]"
msgstr "HAProxy 节点列表 [%s]"
msgid "ACL list [%s]"
msgstr "访问控制列表 [%s]"
msgid "Shunt [%s] node"
msgstr "分流 [%s] 节点"
msgid "Xray Load Balancing node [%s] list"
msgstr "Xray 负载均衡节点 [%s] 列表"
msgid "Xray Load Balancing node [%s] backup node"
msgstr "Xray 负载均衡节点 [%s] 后备节点"
msgid "Sing-Box URLTest node [%s] list"
msgstr "Sing-Box URLTest 节点 [%s] 列表"
msgid "Node [%s] preproxy node"
msgstr "节点 [%s] 前置代理节点"
msgid "Node [%s] landing node"
msgstr "节点 [%s] 落地节点"
msgid "Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings."
msgstr "跳过 %s 节点,因未适配到 %s 核心程序,或未正确设置节点使用类型。"
msgid "Skip node: %s. Because Sing-Box does not support the %s protocol's %s transmission method, Xray needs to be used instead."
msgstr "跳过节点:%s因 Sing-Box 不支持 %s 协议的 %s 传输方式,需更换 Xray。"
msgid "Xray unsupport %s plugin."
msgstr "Xray 不支持 %s 插件。"
msgid "Plugin options Incomplete."
msgstr "插件选项不完整。"
msgid "shadowsocks-libev unsupport 2022 encryption."
msgstr "shadowsocks-libev 不支持2022加密。"
msgid "Please replace Xray or Sing-Box to support more transmission methods in Shadowsocks."
msgstr "请更换 Xray 或 Sing-Box 来支持 Shadowsocks 更多的传输方式。"
msgid "unsupport %s plugin."
msgstr "不支持 %s 插件。"
msgid "Skip the %s node because the %s core program is not installed."
msgstr "跳过 %s 节点,因未安装 %s 核心程序 %s。"
msgid "Skip the %s node, as %s type nodes require Sing-Box version 1.12 or higher."
msgstr "跳过 %s 节点,因 %s 类型的节点需要 Sing-Box 1.12 以上版本支持。"
msgid "%s type node subscriptions are not currently supported, skip this node."
msgstr "暂时不支持 %s 类型的节点订阅,跳过此节点。"
msgid "Update [%s]"
msgstr "'更新【%s】"
msgid "Matching node:"
msgstr "匹配节点:"
msgid "First Matching node:"
msgstr "第一匹配节点:"
msgid "Second Matching node:"
msgstr "第二匹配节点:"
msgid "Third Matching node:"
msgstr "第三匹配节点:"
msgid "Fourth Matching node:"
msgstr "第四匹配节点:"
msgid "Fifth Matching node:"
msgstr "第五匹配节点:"
msgid "Unable to find the best matching node, now replaced with:"
msgstr "无法找到最匹配的节点,当前已更换为:"
msgid "No node information updates are available."
msgstr "没有可用的节点信息更新。"
msgid "Skip unknown types:"
msgstr "跳过未知类型:"
msgid "Discard node: %s, Reason:"
msgstr "丢弃节点: %s ,原因:"
msgid "No usable binary was found."
msgstr "找不到可使用二进制。"
msgid "Discard filter nodes: %s type node %s"
msgstr "丢弃过滤节点: %s 类型节点 %s"
msgid "Parsing error, skip this node."
msgstr "解析错误,跳过此节点。"
msgid "Successfully resolved the [%s] node, number: %s"
msgstr "成功解析【%s】节点数量%s"
msgid "Get subscription content for [%s] is empty. This may be due to an invalid subscription address or a network problem. Please diagnose the issue!"
msgstr "获取到的【%s】订阅内容为空可能是订阅地址无效或是网络问题请诊断"
msgid "Proxy"
msgstr "代理"
msgid "Start subscribing..."
msgstr "开始订阅..."
msgid "Start subscribing: %s"
msgstr "开始订阅:%s"
msgid "Subscription: [%s] No changes, no update required."
msgstr "订阅:【%s】没有变化无需更新。"
msgid "[%s] Subscription failed. This could be due to an invalid subscription address or a network issue. Please diagnose the problem! [%s]"
msgstr "【%s】订阅失败可能是订阅地址无效或是网络问题请诊断[%s]"
msgid "Error, restoring service."
msgstr "发生错误, 正在恢复服务。"
msgid "Subscription complete..."
msgstr "订阅完毕..."

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
zh-tw

View File

@@ -35,7 +35,7 @@ unlock() {
boot_func() { boot_func() {
local delay=$(uci -q get ${CONFIG}.@global_delay[0].start_delay || echo 1) local delay=$(uci -q get ${CONFIG}.@global_delay[0].start_delay || echo 1)
if [ "$delay" -gt 0 ]; then if [ "$delay" -gt 0 ]; then
$APP_FILE echolog "执行启动延时 $delay 秒后再启动!" $APP_FILE log_i18n 0 "Start after a delay of %s seconds!" "${delay}"
sleep $delay sleep $delay
fi fi
restart restart
@@ -48,7 +48,7 @@ boot() {
start() { start() {
set_lock set_lock
[ $? == 1 ] && $APP_FILE echolog "脚本已经在运行,不重复运行,退出." && exit 0 [ $? == 1 ] && $APP_FILE log_i18n 0 "The script is already running, do not run it again. Exit." && exit 0
$APP_FILE start $APP_FILE start
unset_lock unset_lock
} }
@@ -56,14 +56,14 @@ start() {
stop() { stop() {
unlock unlock
set_lock set_lock
[ $? == 1 ] && $APP_FILE echolog "停止脚本等待超时,不重复运行,退出." && exit 0 [ $? == 1 ] && $APP_FILE log_i18n 0 "Stop the script and wait for a timeout, then exit without repeating the process." && exit 0
$APP_FILE stop $APP_FILE stop
unset_lock unset_lock
} }
restart() { restart() {
set_lock set_lock
[ $? == 1 ] && $APP_FILE echolog "脚本已经在运行,不重复运行,退出." && exit 0 [ $? == 1 ] && $APP_FILE log_i18n 0 "The script is already running, do not run it again. Exit." && exit 0
$APP_FILE stop $APP_FILE stop
$APP_FILE start $APP_FILE start
unset_lock unset_lock

View File

@@ -87,35 +87,35 @@ config nodes 'myshunt'
config shunt_rules 'DirectGame' config shunt_rules 'DirectGame'
option remarks 'DirectGame' option remarks 'DirectGame'
option network 'tcp,udp' option network 'tcp,udp'
option domain_list '# steam直连域名获取国内CDN走国内线路下载 option domain_list '# Steam CDN
cm.steampowered.com cm.steampowered.com
steamserver.net steamserver.net
# steam国内CDN华为云 # Steam CN Huawei CDN
steampipe.steamcontent.tnkjmec.com steampipe.steamcontent.tnkjmec.com
# steam国内CDN白山云 # Steam CN BaiShan CDN
st.dl.eccdnx.com st.dl.eccdnx.com
st.dl.bscstorage.net st.dl.bscstorage.net
st.dl.pinyuncloud.com st.dl.pinyuncloud.com
# steam国内CDN新流云(原金山云)(支持ipv6) # Steam CN XinLiuYun(support ipv6) CDN
dl.steam.clngaa.com dl.steam.clngaa.com
# steam国内CDN网宿 # Steam CN Wangsu CDN
cdn.mileweb.cs.steampowered.com.8686c.com cdn.mileweb.cs.steampowered.com.8686c.com
cdn-ws.content.steamchina.com cdn-ws.content.steamchina.com
# steam国内CDN腾讯云 (蒸汽中国独占) # Steam CN Tencent CDN
cdn-qc.content.steamchina.com cdn-qc.content.steamchina.com
# steam国内CDN阿里云(支持ipv6) # Steam CN Aliyun(support ipv6) CDN
cdn-ali.content.steamchina.com cdn-ali.content.steamchina.com
xz.pphimalayanrt.com xz.pphimalayanrt.com
lv.queniujq.cn lv.queniujq.cn
alibaba.cdn.steampipe.steamcontent.com alibaba.cdn.steampipe.steamcontent.com
# 国内游戏geosite域名 # CN Game geosite domain
geosite:category-games@cn geosite:category-games@cn
rule-set:remote:https://raw.githubusercontent.com/lyc8503/sing-box-rules/rule-set-geosite/geosite-category-games%40cn.srs rule-set:remote:https://raw.githubusercontent.com/lyc8503/sing-box-rules/rule-set-geosite/geosite-category-games%40cn.srs
' '
option ip_list '# steam直连IP option ip_list '# Steam IP
45.121.184.0/24 45.121.184.0/24
103.10.124.0/23 103.10.124.0/23
103.28.54.0/24 103.28.54.0/24

View File

@@ -24,9 +24,38 @@ UTIL_NAIVE=$LUA_UTIL_PATH/util_naiveproxy.lua
UTIL_HYSTERIA2=$LUA_UTIL_PATH/util_hysteria2.lua UTIL_HYSTERIA2=$LUA_UTIL_PATH/util_hysteria2.lua
UTIL_TUIC=$LUA_UTIL_PATH/util_tuic.lua UTIL_TUIC=$LUA_UTIL_PATH/util_tuic.lua
i18n() {
echo "$(lua ${APP_PATH}/i18n.lua "$@")"
}
echolog() { echolog() {
echo -e "$*" >>$LOG_FILE
}
echolog_date() {
local d="$(date "+%Y-%m-%d %H:%M:%S")" local d="$(date "+%Y-%m-%d %H:%M:%S")"
echo -e "$d: $*" >>$LOG_FILE echolog "$d: $*"
}
log() {
local num="$1"
shift
local content="$@"
local indent=""
if [ "$num" -ge 1 ]; then
for i in $(seq 1 ${num}); do
indent="${indent} "
done
echolog_date "${indent}- ${content}"
else
echolog_date "${content}"
fi
}
log_i18n() {
local num="$1"
shift
log ${num} "$(i18n "$@")"
} }
config_get_type() { config_get_type() {
@@ -147,7 +176,7 @@ check_host() {
local f=${1} local f=${1}
a=$(echo $f | grep "\/") a=$(echo $f | grep "\/")
[ -n "$a" ] && return 1 [ -n "$a" ] && return 1
# 判断是否包含汉字~ # Determine if it contains Chinese characters.
local tmp=$(echo -n $f | awk '{print gensub(/[!-~]/,"","g",$0)}') local tmp=$(echo -n $f | awk '{print gensub(/[!-~]/,"","g",$0)}')
[ -n "$tmp" ] && return 1 [ -n "$tmp" ] && return 1
return 0 return 0
@@ -216,11 +245,11 @@ check_depends() {
[ -d "/lib/apk/packages" ] && file_path="/lib/apk/packages" && file_ext=".list" [ -d "/lib/apk/packages" ] && file_path="/lib/apk/packages" && file_ext=".list"
if [ "$tables" == "iptables" ]; then if [ "$tables" == "iptables" ]; then
for depends in "iptables-mod-tproxy" "iptables-mod-socket" "iptables-mod-iprange" "iptables-mod-conntrack-extra" "kmod-ipt-nat"; do for depends in "iptables-mod-tproxy" "iptables-mod-socket" "iptables-mod-iprange" "iptables-mod-conntrack-extra" "kmod-ipt-nat"; do
[ -s "${file_path}/${depends}${file_ext}" ] || echolog "$tables透明代理基础依赖 $depends 未安装..." [ -s "${file_path}/${depends}${file_ext}" ] || log_i18n 0 "%s Transparent proxy base dependencies %s Not installed..." "${tables}" "${depends}"
done done
else else
for depends in "kmod-nft-socket" "kmod-nft-tproxy" "kmod-nft-nat"; do for depends in "kmod-nft-socket" "kmod-nft-tproxy" "kmod-nft-nat"; do
[ -s "${file_path}/${depends}${file_ext}" ] || echolog "$tables透明代理基础依赖 $depends 未安装..." [ -s "${file_path}/${depends}${file_ext}" ] || log_i18n 0 "%s Transparent proxy base dependencies %s Not installed..." "${tables}" "${depends}"
done done
fi fi
} }
@@ -257,14 +286,14 @@ ln_run() {
ln -s "${file_func}" "${TMP_BIN_PATH}/${ln_name}" >/dev/null 2>&1 ln -s "${file_func}" "${TMP_BIN_PATH}/${ln_name}" >/dev/null 2>&1
file_func="${TMP_BIN_PATH}/${ln_name}" file_func="${TMP_BIN_PATH}/${ln_name}"
} }
[ -x "${file_func}" ] || echolog " - $(readlink ${file_func}) 没有执行权限,无法启动:${file_func} $*" [ -x "${file_func}" ] || log 1 "$(i18n "%s does not have execute permissions and cannot be started: %s %s" "$(readlink ${file_func})" "${file_func}" "$*")"
fi fi
#echo "${file_func} $*" >&2 #echo "${file_func} $*" >&2
[ -n "${file_func}" ] || echolog " - 找不到 ${ln_name},无法启动..." [ -n "${file_func}" ] || log 1 "$(i18n "%s not found, unable to start..." "${ln_name}")"
${file_func:-echolog " - ${ln_name}"} "$@" >${output} 2>&1 & ${file_func:-log 1 "${ln_name}"} "$@" >${output} 2>&1 &
process_count=$(ls $TMP_SCRIPT_FUNC_PATH | grep -v "^_" | wc -l) process_count=$(ls $TMP_SCRIPT_FUNC_PATH | grep -v "^_" | wc -l)
process_count=$((process_count + 1)) process_count=$((process_count + 1))
echo "${file_func:-echolog " - ${ln_name}"} $@ >${output}" > $TMP_SCRIPT_FUNC_PATH/$process_count echo "${file_func:-log 1 "${ln_name}"} $@ >${output}" > $TMP_SCRIPT_FUNC_PATH/$process_count
} }
lua_api() { lua_api() {
@@ -588,12 +617,12 @@ run_socks() {
if [ -n "$server_host" ] && [ -n "$server_port" ]; then if [ -n "$server_host" ] && [ -n "$server_port" ]; then
check_host $server_host check_host $server_host
[ $? != 0 ] && { [ $? != 0 ] && {
echolog " - Socks节点[$remarks]${server_host} 是非法的服务器地址,无法启动!" log 1 "$(i18n "Socks node: [%s]%s is an invalid server address and cannot be started!" "${$remarks}" "${server_host}")"
return 1 return 1
} }
tmp="${server_host}:${server_port}" tmp="${server_host}:${server_port}"
else else
error_msg="某种原因,此 Socks 服务的相关配置已失联,启动中止!" error_msg="$(i18n "For some reason, the configuration for this Socks service has been lost, and its startup has been aborted!")"
fi fi
if [ "$type" == "sing-box" ] || [ "$type" == "xray" ]; then if [ "$type" == "sing-box" ] || [ "$type" == "xray" ]; then
@@ -604,10 +633,10 @@ run_socks() {
fi fi
[ -n "${error_msg}" ] && { [ -n "${error_msg}" ] && {
[ "$bind" != "127.0.0.1" ] && echolog " - Socks节点[$remarks]${tmp},启动中止 ${bind}:${socks_port} ${error_msg}" [ "$bind" != "127.0.0.1" ] && log 1 "$(i18n "Socks node: [%s]%s, start failed %s:%s %s" "${remarks}" "${tmp}" "${bind}" "${socks_port}" "${error_msg}")"
return 1 return 1
} }
[ "$bind" != "127.0.0.1" ] && echolog " - Socks节点[$remarks]${tmp},启动 ${bind}:${socks_port}" [ "$bind" != "127.0.0.1" ] && log 1 "$(i18n "Socks node: [%s]%s, starting %s:%s" "${remarks}" "${tmp}" "${bind}" "${socks_port}")"
case "$type" in case "$type" in
sing-box) sing-box)
@@ -707,7 +736,7 @@ socks_node_switch() {
eval_set_val $@ eval_set_val $@
[ -n "$flag" ] && [ -n "$new_node" ] && { [ -n "$flag" ] && [ -n "$new_node" ] && {
local prefix pf filename local prefix pf filename
# 结束 SS 插件进程 # Kill the SS plugin process
for prefix in "" "HTTP_"; do for prefix in "" "HTTP_"; do
pf="$TMP_PATH/${prefix}SOCKS_${flag}_plugin.pid" pf="$TMP_PATH/${prefix}SOCKS_${flag}_plugin.pid"
[ -s "$pf" ] && kill -9 "$(head -n1 "$pf")" >/dev/null 2>&1 [ -s "$pf" ] && kill -9 "$(head -n1 "$pf")" >/dev/null 2>&1
@@ -747,7 +776,7 @@ run_global() {
mkdir -p ${GLOBAL_ACL_PATH} mkdir -p ${GLOBAL_ACL_PATH}
if [ $PROXY_IPV6 == "1" ]; then if [ $PROXY_IPV6 == "1" ]; then
echolog "开启实验性IPv6透明代理(TProxy),请确认您的节点及类型支持IPv6" log_i18n 0 "To enable experimental IPv6 transparent proxy (TProxy), please ensure your node and type support IPv6!"
fi fi
TUN_DNS_PORT=15353 TUN_DNS_PORT=15353
@@ -755,23 +784,23 @@ run_global() {
V2RAY_ARGS="flag=global node=$NODE redir_port=$REDIR_PORT tcp_proxy_way=${TCP_PROXY_WAY}" V2RAY_ARGS="flag=global node=$NODE redir_port=$REDIR_PORT tcp_proxy_way=${TCP_PROXY_WAY}"
V2RAY_ARGS="${V2RAY_ARGS} dns_listen_port=${TUN_DNS_PORT} direct_dns_query_strategy=${DIRECT_DNS_QUERY_STRATEGY} remote_dns_query_strategy=${REMOTE_DNS_QUERY_STRATEGY} dns_cache=${DNS_CACHE}" V2RAY_ARGS="${V2RAY_ARGS} dns_listen_port=${TUN_DNS_PORT} direct_dns_query_strategy=${DIRECT_DNS_QUERY_STRATEGY} remote_dns_query_strategy=${REMOTE_DNS_QUERY_STRATEGY} dns_cache=${DNS_CACHE}"
local msg="${TUN_DNS} 直连DNS${AUTO_DNS}" local msg="DNS: ${TUN_DNS} $(i18n "Direct DNS: %s" "${AUTO_DNS}")"
[ -n "$REMOTE_DNS_PROTOCOL" ] && { [ -n "$REMOTE_DNS_PROTOCOL" ] && {
V2RAY_ARGS="${V2RAY_ARGS} remote_dns_protocol=${REMOTE_DNS_PROTOCOL} remote_dns_detour=${REMOTE_DNS_DETOUR}" V2RAY_ARGS="${V2RAY_ARGS} remote_dns_protocol=${REMOTE_DNS_PROTOCOL} remote_dns_detour=${REMOTE_DNS_DETOUR}"
case "$REMOTE_DNS_PROTOCOL" in case "$REMOTE_DNS_PROTOCOL" in
udp*) udp*)
V2RAY_ARGS="${V2RAY_ARGS} remote_dns_udp_server=${REMOTE_DNS}" V2RAY_ARGS="${V2RAY_ARGS} remote_dns_udp_server=${REMOTE_DNS}"
msg="${msg} 远程DNS${REMOTE_DNS}" msg="${msg} $(i18n "Remote DNS: %s" "${REMOTE_DNS}")"
;; ;;
tcp) tcp)
V2RAY_ARGS="${V2RAY_ARGS} remote_dns_tcp_server=${REMOTE_DNS}" V2RAY_ARGS="${V2RAY_ARGS} remote_dns_tcp_server=${REMOTE_DNS}"
msg="${msg} 远程DNS${REMOTE_DNS}" msg="${msg} $(i18n "Remote DNS: %s" "${REMOTE_DNS}")"
;; ;;
doh) doh)
REMOTE_DNS_DOH=$(config_t_get global remote_dns_doh "https://1.1.1.1/dns-query") REMOTE_DNS_DOH=$(config_t_get global remote_dns_doh "https://1.1.1.1/dns-query")
V2RAY_ARGS="${V2RAY_ARGS} remote_dns_doh=${REMOTE_DNS_DOH}" V2RAY_ARGS="${V2RAY_ARGS} remote_dns_doh=${REMOTE_DNS_DOH}"
msg="${msg} 远程DNS${REMOTE_DNS_DOH}" msg="${msg} $(i18n "Remote DNS: %s" "${REMOTE_DNS_DOH}")"
;; ;;
esac esac
[ "$REMOTE_FAKEDNS" = "1" ] && { [ "$REMOTE_FAKEDNS" = "1" ] && {
@@ -783,7 +812,7 @@ run_global() {
[ -n "${_remote_dns_client_ip}" ] && V2RAY_ARGS="${V2RAY_ARGS} remote_dns_client_ip=${_remote_dns_client_ip}" [ -n "${_remote_dns_client_ip}" ] && V2RAY_ARGS="${V2RAY_ARGS} remote_dns_client_ip=${_remote_dns_client_ip}"
} }
msg="${msg}" msg="${msg}"
echolog ${msg} log 0 ${msg}
V2RAY_CONFIG=${GLOBAL_ACL_PATH}/global.json V2RAY_CONFIG=${GLOBAL_ACL_PATH}/global.json
V2RAY_LOG=${GLOBAL_ACL_PATH}/global.log V2RAY_LOG=${GLOBAL_ACL_PATH}/global.log
@@ -855,7 +884,7 @@ start_socks() {
[ "$SOCKS_ENABLED" = "1" ] && { [ "$SOCKS_ENABLED" = "1" ] && {
local ids=$(uci show $CONFIG | grep "=socks" | awk -F '.' '{print $2}' | awk -F '=' '{print $1}') local ids=$(uci show $CONFIG | grep "=socks" | awk -F '.' '{print $2}' | awk -F '=' '{print $1}')
[ -n "$ids" ] && { [ -n "$ids" ] && {
echolog "分析 Socks 服务的节点配置..." log_i18n 0 "Analyzing the node configuration of the Socks service..."
for id in $ids; do for id in $ids; do
local enabled=$(config_n_get $id enabled 0) local enabled=$(config_n_get $id enabled 0)
[ "$enabled" == "0" ] && continue [ "$enabled" == "0" ] && continue
@@ -874,7 +903,7 @@ start_socks() {
run_socks flag=$id node=$node bind=$bind socks_port=$port config_file=$config_file http_port=$http_port http_config_file=$http_config_file log_file=$log_file run_socks flag=$id node=$node bind=$bind socks_port=$port config_file=$config_file http_port=$http_port http_config_file=$http_config_file log_file=$log_file
set_cache_var "socks_${id}" "$node" set_cache_var "socks_${id}" "$node"
#自动切换逻辑 # Auto switch logic
local enable_autoswitch=$(config_n_get $id enable_autoswitch 0) local enable_autoswitch=$(config_n_get $id enable_autoswitch 0)
[ "$enable_autoswitch" = "1" ] && $APP_PATH/socks_auto_switch.sh ${id} > /dev/null 2>&1 & [ "$enable_autoswitch" = "1" ] && $APP_PATH/socks_auto_switch.sh ${id} > /dev/null 2>&1 &
done done
@@ -886,7 +915,7 @@ clean_log() {
logsnum=$(cat $LOG_FILE 2>/dev/null | wc -l) logsnum=$(cat $LOG_FILE 2>/dev/null | wc -l)
[ "$logsnum" -gt 1000 ] && { [ "$logsnum" -gt 1000 ] && {
echo "" > $LOG_FILE echo "" > $LOG_FILE
echolog "日志文件过长,清空处理!" log_i18n 0 "Log file is too long, clear it!"
} }
} }
@@ -910,7 +939,7 @@ start_crontab() {
[ -f "/tmp/lock/${CONFIG}_cron.lock" ] && { [ -f "/tmp/lock/${CONFIG}_cron.lock" ] && {
rm -rf "/tmp/lock/${CONFIG}_cron.lock" rm -rf "/tmp/lock/${CONFIG}_cron.lock"
echolog "当前为计划任务自动运行,不重新配置定时任务。" log_i18n 0 "The task is currently running automatically as a scheduled task; no reconfiguration of the scheduled task is required."
return return
} }
@@ -931,7 +960,7 @@ start_crontab() {
else else
echo "$t /etc/init.d/$CONFIG stop > /dev/null 2>&1 &" >>/etc/crontabs/root echo "$t /etc/init.d/$CONFIG stop > /dev/null 2>&1 &" >>/etc/crontabs/root
fi fi
echolog "配置定时任务:自动关闭服务。" log_i18n 0 "Scheduled tasks: Auto stop service."
fi fi
start_week_mode=$(config_t_get global_delay start_week_mode) start_week_mode=$(config_t_get global_delay start_week_mode)
@@ -944,7 +973,7 @@ start_crontab() {
else else
echo "$t /etc/init.d/$CONFIG start > /dev/null 2>&1 &" >>/etc/crontabs/root echo "$t /etc/init.d/$CONFIG start > /dev/null 2>&1 &" >>/etc/crontabs/root
fi fi
echolog "配置定时任务:自动开启服务。" log_i18n 0 "Scheduled tasks: Auto start service."
fi fi
restart_week_mode=$(config_t_get global_delay restart_week_mode) restart_week_mode=$(config_t_get global_delay restart_week_mode)
@@ -957,7 +986,7 @@ start_crontab() {
else else
echo "$t /etc/init.d/$CONFIG restart > /dev/null 2>&1 &" >>/etc/crontabs/root echo "$t /etc/init.d/$CONFIG restart > /dev/null 2>&1 &" >>/etc/crontabs/root
fi fi
echolog "配置定时任务:自动重启服务。" log_i18n 0 "Scheduled tasks: Auto restart service."
fi fi
autoupdate=$(config_t_get global_rules auto_update) autoupdate=$(config_t_get global_rules auto_update)
@@ -971,7 +1000,7 @@ start_crontab() {
else else
echo "$t lua $APP_PATH/rule_update.lua log all cron > /dev/null 2>&1 &" >>/etc/crontabs/root echo "$t lua $APP_PATH/rule_update.lua log all cron > /dev/null 2>&1 &" >>/etc/crontabs/root
fi fi
echolog "配置定时任务:自动更新规则。" log_i18n 0 "Scheduled tasks: Auto update rules."
fi fi
TMP_SUB_PATH=$TMP_PATH/sub_crontabs TMP_SUB_PATH=$TMP_PATH/sub_crontabs
@@ -983,7 +1012,7 @@ start_crontab() {
week_update=$(config_n_get $item week_update) week_update=$(config_n_get $item week_update)
time_update=$(config_n_get $item time_update) time_update=$(config_n_get $item time_update)
echo "$cfgid" >> $TMP_SUB_PATH/${week_update}_${time_update} echo "$cfgid" >> $TMP_SUB_PATH/${week_update}_${time_update}
echolog "配置定时任务:自动更新【$remark】订阅。" log_i18n 0 "Scheduled tasks: Auto update [%s] subscription." "${remark}"
fi fi
done done
@@ -1006,10 +1035,10 @@ start_crontab() {
if [ "$ENABLED_DEFAULT_ACL" == 1 ] || [ "$ENABLED_ACLS" == 1 ]; then if [ "$ENABLED_DEFAULT_ACL" == 1 ] || [ "$ENABLED_ACLS" == 1 ]; then
[ "$update_loop" = "1" ] && { [ "$update_loop" = "1" ] && {
$APP_PATH/tasks.sh > /dev/null 2>&1 & $APP_PATH/tasks.sh > /dev/null 2>&1 &
echolog "自动更新:启动循环更新进程。" log_i18n 0 "Auto updates: Starts a cyclical update process."
} }
else else
echolog "运行于非代理模式,仅允许服务启停的定时任务。" log_i18n 0 "Running in no proxy mode, it only allows scheduled tasks for starting and stopping services."
fi fi
/etc/init.d/cron restart /etc/init.d/cron restart
@@ -1019,13 +1048,13 @@ stop_crontab() {
[ -f "/tmp/lock/${CONFIG}_cron.lock" ] && return [ -f "/tmp/lock/${CONFIG}_cron.lock" ] && return
clean_crontab clean_crontab
/etc/init.d/cron restart /etc/init.d/cron restart
#echolog "清除定时执行命令。" #log_i18n 0 "Clear scheduled commands."
} }
add_ip2route() { add_ip2route() {
local ip=$(get_host_ip "ipv4" $1) local ip=$(get_host_ip "ipv4" $1)
[ -z "$ip" ] && { [ -z "$ip" ] && {
echolog " - 无法解析[${1}],路由表添加失败!" log 1 "$(i18n "Unable to resolve [%s], route table addition failed!" "${1}")"
return 1 return 1
} }
local remarks="${1}" local remarks="${1}"
@@ -1040,9 +1069,9 @@ add_ip2route() {
if [ -n "${gateway}" ]; then if [ -n "${gateway}" ]; then
route add -host ${ip} gw ${gateway} dev ${device} >/dev/null 2>&1 route add -host ${ip} gw ${gateway} dev ${device} >/dev/null 2>&1
echo "$ip" >> $TMP_ROUTE_PATH/${device} echo "$ip" >> $TMP_ROUTE_PATH/${device}
echolog " - [${remarks}]添加到接口[${device}]路由表成功!" log 1 "$(i18n "[%s] was successfully added to the routing table of interface [%s]!" "${remarks}" "${device}")"
else else
echolog " - [${remarks}]添加到接口[${device}]路由表失功!原因是找不到[${device}]网关。" log 1 "$(i18n "Adding [%s] to the [%s] routing table failed! The reason is that the [%s] gateway cannot be found." "${remarks}" "${device}" "${device}")"
fi fi
} }
@@ -1218,7 +1247,7 @@ acl_app() {
set_cache_var "ACL_${sid}_dns_port" "${GLOBAL_DNSMASQ_PORT}" set_cache_var "ACL_${sid}_dns_port" "${GLOBAL_DNSMASQ_PORT}"
set_cache_var "ACL_${sid}_default" "1" set_cache_var "ACL_${sid}_default" "1"
else else
echolog " - 全局节点未启用,跳过【${remarks}" log 1 "$(i18n "Global nodes are not enabled, skip [%s]." "${remarks}")"
fi fi
else else
[ "$(config_get_type $node)" = "nodes" ] && { [ "$(config_get_type $node)" = "nodes" ] && {
@@ -1265,7 +1294,7 @@ acl_app() {
start() { start() {
pgrep -f /tmp/etc/passwall2/bin > /dev/null 2>&1 && { pgrep -f /tmp/etc/passwall2/bin > /dev/null 2>&1 && {
#echolog "程序已启动,先停止再重新启动!" #log_i18n 0 "The program has started. Please stop it and then restart it!"
stop stop
} }
mkdir -p /tmp/etc /tmp/log $TMP_PATH $TMP_BIN_PATH $TMP_SCRIPT_FUNC_PATH $TMP_ROUTE_PATH $TMP_ACL_PATH $TMP_PATH2 mkdir -p /tmp/etc /tmp/log $TMP_PATH $TMP_BIN_PATH $TMP_SCRIPT_FUNC_PATH $TMP_ROUTE_PATH $TMP_ACL_PATH $TMP_PATH2
@@ -1284,9 +1313,9 @@ start() {
if [ -n "$(command -v iptables-legacy || command -v iptables)" ] && [ -n "$(command -v ipset)" ] && [ -n "$(dnsmasq --version | grep 'Compile time options:.* ipset')" ]; then if [ -n "$(command -v iptables-legacy || command -v iptables)" ] && [ -n "$(command -v ipset)" ] && [ -n "$(dnsmasq --version | grep 'Compile time options:.* ipset')" ]; then
USE_TABLES="iptables" USE_TABLES="iptables"
else else
echolog "系统未安装iptablesipset或Dnsmasq没有开启ipset支持无法使用iptables+ipset透明代理" log_i18n 0 "The system does not have iptables or ipset installed, or Dnsmasq does not have ipset support enabled, so iptables+ipset transparent proxy cannot be used!"
if [ -n "$(command -v fw4)" ] && [ -n "$(command -v nft)" ] && [ -n "$(dnsmasq --version | grep 'Compile time options:.* nftset')" ]; then if [ -n "$(command -v fw4)" ] && [ -n "$(command -v nft)" ] && [ -n "$(dnsmasq --version | grep 'Compile time options:.* nftset')" ]; then
echolog "检测到fw4使用nftables进行透明代理。" log_i18n 0 "fw4 detected, use nftables to transparent proxy."
USE_TABLES="nftables" USE_TABLES="nftables"
nftflag=1 nftflag=1
config_t_set global_forwarding use_nft 1 config_t_set global_forwarding use_nft 1
@@ -1298,7 +1327,7 @@ start() {
USE_TABLES="nftables" USE_TABLES="nftables"
nftflag=1 nftflag=1
else else
echolog "Dnsmasq软件包不满足nftables透明代理要求如需使用请确保dnsmasq版本在2.87以上并开启nftset支持。" log_i18n 0 "The Dnsmasq package does not meet the requirements for transparent proxy in nftables. If you need to use it, please ensure that the dnsmasq version is 2.87 or higher and that nftset support is enabled."
fi fi
fi fi
@@ -1306,7 +1335,7 @@ start() {
[ "$USE_TABLES" = "nftables" ] && { [ "$USE_TABLES" = "nftables" ] && {
dnsmasq_version=$(dnsmasq -v | grep -i "Dnsmasq version " | awk '{print $3}') dnsmasq_version=$(dnsmasq -v | grep -i "Dnsmasq version " | awk '{print $3}')
[ "$(expr $dnsmasq_version \>= 2.90)" == 0 ] && echolog "Dnsmasq版本低于2.90建议升级至2.90及以上版本以避免部分情况下Dnsmasq崩溃问题" [ "$(expr $dnsmasq_version \>= 2.90)" == 0 ] && log_i18n 0 "If your Dnsmasq version is lower than 2.90, it is recommended to upgrade to version 2.90 or higher to avoid Dnsmasq crashing in some cases!"
} }
if [ "$ENABLED_DEFAULT_ACL" == 1 ] || [ "$ENABLED_ACLS" == 1 ]; then if [ "$ENABLED_DEFAULT_ACL" == 1 ] || [ "$ENABLED_ACLS" == 1 ]; then
@@ -1332,7 +1361,8 @@ start() {
} }
fi fi
start_crontab start_crontab
echolog "运行完成!\n" log_i18n 0 "Running complete!"
echolog "\n"
} }
stop() { stop() {
@@ -1340,7 +1370,7 @@ stop() {
eval_cache_var eval_cache_var
[ -n "$USE_TABLES" ] && source $APP_PATH/${USE_TABLES}.sh stop [ -n "$USE_TABLES" ] && source $APP_PATH/${USE_TABLES}.sh stop
delete_ip2route delete_ip2route
# 结束 SS 插件进程 # Kill the SS plugin process
# kill_all xray-plugin v2ray-plugin obfs-local shadow-tls # kill_all xray-plugin v2ray-plugin obfs-local shadow-tls
local pid_file pid local pid_file pid
find "$TMP_PATH" -type f -name '*_plugin.pid' 2>/dev/null | while read -r pid_file; do find "$TMP_PATH" -type f -name '*_plugin.pid' 2>/dev/null | while read -r pid_file; do
@@ -1376,7 +1406,7 @@ stop() {
rm -rf $TMP_PATH rm -rf $TMP_PATH
rm -rf /tmp/lock/${CONFIG}_socks_auto_switch* rm -rf /tmp/lock/${CONFIG}_socks_auto_switch*
rm -rf /tmp/lock/${CONFIG}_lease2hosts* rm -rf /tmp/lock/${CONFIG}_lease2hosts*
echolog "清空并关闭相关程序和缓存完成。" log_i18n 0 "Clearing and closing related programs and cache complete."
exit 0 exit 0
} }
@@ -1447,8 +1477,14 @@ case $arg1 in
add_ip2route) add_ip2route)
add_ip2route $@ add_ip2route $@
;; ;;
echolog) log)
echolog $@ log $@
;;
log_i18n)
log_i18n "$@"
;;
i18n)
i18n "$@"
;; ;;
get_new_port) get_new_port)
get_new_port $@ get_new_port $@

View File

@@ -40,8 +40,8 @@ local bind_local = uci:get(appname, "@global_haproxy[0]", "bind_local") or "0"
local bind_address = "0.0.0.0" local bind_address = "0.0.0.0"
if bind_local == "1" then bind_address = "127.0.0.1" end if bind_local == "1" then bind_address = "127.0.0.1" end
log("HAPROXY 负载均衡:") log("HAProxy: ")
log(string.format(" * 控制台端口:%s", console_port)) log(" * " .. api.i18n.translatef("Console Port: %s", console_port))
fs.mkdir(haproxy_path) fs.mkdir(haproxy_path)
local haproxy_file = haproxy_path .. "/" .. haproxy_conf local haproxy_file = haproxy_path .. "/" .. haproxy_conf
@@ -150,7 +150,7 @@ uci:foreach(appname, "haproxy_config", function(t)
t.server_port = server_port t.server_port = server_port
table.insert(listens[listen_port], t) table.insert(listens[listen_port], t)
else else
log(" - 丢弃1个明显无效的节点") log(" - " .. api.i18n.translate("Discard one obviously invalid node."))
end end
end end
end) end)
@@ -164,7 +164,7 @@ end
table.sort(sortTable, function(a,b) return (a < b) end) table.sort(sortTable, function(a,b) return (a < b) end)
for i, port in pairs(sortTable) do for i, port in pairs(sortTable) do
log(" + 入口 %s:%s" % {bind_address, port}) log(" + " .. api.i18n.translatef("Entrance %s:%s", bind_address, port))
f_out:write("\n" .. string.format([[ f_out:write("\n" .. string.format([[
listen %s listen %s
@@ -183,7 +183,7 @@ listen %s
local count_M, count_B = 1, 1 local count_M, count_B = 1, 1
for i, o in ipairs(listens[port]) do for i, o in ipairs(listens[port]) do
local remark = o.server_remark or "" local remark = o.server_remark or ""
-- 防止重名导致无法运行 -- To prevent duplicate names from causing the program to fail to run.
if tostring(o.backup) ~= "1" then if tostring(o.backup) ~= "1" then
remark = "M" .. count_M .. "-" .. remark remark = "M" .. count_M .. "-" .. remark
count_M = count_M + 1 count_M = count_M + 1
@@ -210,11 +210,11 @@ listen %s
sys.call(string.format("/usr/share/passwall2/app.sh add_ip2route %s %s", o.origin_address, o.export)) sys.call(string.format("/usr/share/passwall2/app.sh add_ip2route %s %s", o.origin_address, o.export))
end end
log(string.format(" | - 出口节点:%s:%s权重%s", o.origin_address, o.origin_port, o.lbweight)) log(string.format(" | - " .. api.i18n.translatef("Node: %s:%s, Weight: %s", o.origin_address, o.origin_port, o.lbweight)))
end end
end end
--控制台配置 -- Console config
local console_user = uci:get(appname, "@global_haproxy[0]", "console_user") local console_user = uci:get(appname, "@global_haproxy[0]", "console_user")
local console_password = uci:get(appname, "@global_haproxy[0]", "console_password") local console_password = uci:get(appname, "@global_haproxy[0]", "console_password")
local str = [[ local str = [[
@@ -230,7 +230,7 @@ f_out:write("\n" .. string.format(str, console_port, (console_user and console_u
f_out:close() f_out:close()
--内置健康检查URL -- Built-in health check URL
if health_check_type == "passwall_logic" then if health_check_type == "passwall_logic" then
local probeUrl = uci:get(appname, "@global_haproxy[0]", "health_probe_url") or "https://www.google.com/generate_204" local probeUrl = uci:get(appname, "@global_haproxy[0]", "health_probe_url") or "https://www.google.com/generate_204"
local f_url = io.open(haproxy_path .. "/Probe_URL", "w") local f_url = io.open(haproxy_path .. "/Probe_URL", "w")

View File

@@ -9,7 +9,6 @@ server_address=$3
server_port=$4 server_port=$4
pgrep -af "${CONFIG}/" | awk '/app\.sh.*(start|stop)/ || /nftables\.sh/ || /iptables\.sh/ { found = 1 } END { exit !found }' && { pgrep -af "${CONFIG}/" | awk '/app\.sh.*(start|stop)/ || /nftables\.sh/ || /iptables\.sh/ { found = 1 } END { exit !found }' && {
# 特定任务执行中不检测
exit 0 exit 0
} }

View File

@@ -84,7 +84,7 @@ function restart(var)
local LOG = var["-LOG"] local LOG = var["-LOG"]
sys.call("/etc/init.d/dnsmasq restart >/dev/null 2>&1") sys.call("/etc/init.d/dnsmasq restart >/dev/null 2>&1")
if LOG == "1" then if LOG == "1" then
api.log("重启 dnsmasq 服务") api.log(api.i18n.translate("Restart dnsmasq service."))
end end
end end
@@ -111,7 +111,7 @@ function logic_restart(var)
sys.call("/etc/init.d/dnsmasq restart >/dev/null 2>&1") sys.call("/etc/init.d/dnsmasq restart >/dev/null 2>&1")
end end
if LOG == "1" then if LOG == "1" then
api.log("重启 dnsmasq 服务") api.log(api.i18n.translate("Restart dnsmasq service."))
end end
end end
@@ -267,7 +267,7 @@ function add_rule(var)
local fwd_dns local fwd_dns
--始终用国内DNS解析节点域名 -- Always use domestic DNS to resolve node domain names
if true then if true then
fwd_dns = LOCAL_DNS fwd_dns = LOCAL_DNS
uci:foreach(appname, "nodes", function(t) uci:foreach(appname, "nodes", function(t)

View File

@@ -0,0 +1,6 @@
if #arg > 0 then
local api = require "luci.passwall2.api"
local str = arg[1]
table.remove(arg, 1)
print(api.i18n.translatef(str, unpack(arg)))
end

View File

@@ -46,7 +46,7 @@ comment() {
echo "-m comment --comment '$name'" echo "-m comment --comment '$name'"
} }
#解决端口超过15个ipt无效支持单端口、端口范围 # Resolves invalid IP addresses for ports exceeding 15; it supports single ports and port ranges.
add_port_rules() { add_port_rules() {
local ipt_cmd="$1" local ipt_cmd="$1"
local port_list="$2" local port_list="$2"
@@ -136,7 +136,7 @@ insert_rule_after() {
RULE_LAST_INDEX() { RULE_LAST_INDEX() {
[ $# -ge 3 ] || { [ $# -ge 3 ] || {
echolog "索引列举方式不正确(iptables),终止执行!" log_i18n 1 "Incorrect index listing method (%s), execution terminated!" "iptables"
return 1 return 1
} }
local ipt_tmp="${1}"; shift local ipt_tmp="${1}"; shift
@@ -168,10 +168,6 @@ get_redirect_ip6t() {
echo "$(REDIRECT $2 $3)" echo "$(REDIRECT $2 $3)"
} }
get_action_chain_name() {
echo "全局代理"
}
gen_lanlist() { gen_lanlist() {
cat <<-EOF cat <<-EOF
0.0.0.0/8 0.0.0.0/8
@@ -275,7 +271,7 @@ gen_shunt_list() {
get_geoip $_geoip_code ipv4 | grep -E "(\.((2(5[0-5]|[0-4][0-9]))|[0-1]?[0-9]{1,2})){3}" | sed -e "s/^/add $ipset_v4 &/g" | awk '{print $0} END{print "COMMIT"}' | ipset -! -R get_geoip $_geoip_code ipv4 | grep -E "(\.((2(5[0-5]|[0-4][0-9]))|[0-1]?[0-9]{1,2})){3}" | sed -e "s/^/add $ipset_v4 &/g" | awk '{print $0} END{print "COMMIT"}' | ipset -! -R
get_geoip $_geoip_code ipv6 | grep -E "([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4}" | sed -e "s/^/add $ipset_v6 &/g" | awk '{print $0} END{print "COMMIT"}' | ipset -! -R get_geoip $_geoip_code ipv6 | grep -E "([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4}" | sed -e "s/^/add $ipset_v6 &/g" | awk '{print $0} END{print "COMMIT"}' | ipset -! -R
fi fi
echolog " - [$?]解析分流规则[$shunt_id]-[geoip:${_geoip_code}]加入到 IPSET 完成" log 1 "$(i18n "parse the traffic splitting rules[%s]-[geoip:%s] add to %s to complete." "${shunt_id}" "${_geoip_code}" "IPSET")"
} }
} }
} }
@@ -321,8 +317,8 @@ add_shunt_t_rule() {
} }
load_acl() { load_acl() {
log_i18n 1 "Access Control:"
[ "$ENABLED_ACLS" == 1 ] && { [ "$ENABLED_ACLS" == 1 ] && {
echolog "访问控制:"
acl_app acl_app
for sid in $(ls -F ${TMP_ACL_PATH} | grep '/$' | awk -F '/' '{print $1}' | grep -v 'default'); do for sid in $(ls -F ${TMP_ACL_PATH} | grep '/$' | awk -F '/' '{print $1}' | grep -v 'default'); do
eval $(uci -q show "${CONFIG}.${sid}" | cut -d'.' -sf 3-) eval $(uci -q show "${CONFIG}.${sid}" | cut -d'.' -sf 3-)
@@ -357,7 +353,7 @@ load_acl() {
ipset -! create $ipset_white nethash maxelem 1048576 ipset -! create $ipset_white nethash maxelem 1048576
ipset -! create $ipset_white6 nethash family inet6 maxelem 1048576 ipset -! create $ipset_white6 nethash family inet6 maxelem 1048576
#分流规则的IP列表(使用分流节点时导入) # Shunt rules IP list (import when use shunt node)
gen_shunt_list "${node}" shunt_list4 shunt_list6 ${write_ipset_direct} ${ipset_white} ${ipset_white6} gen_shunt_list "${node}" shunt_list4 shunt_list6 ${write_ipset_direct} ${ipset_white} ${ipset_white6}
fi fi
} }
@@ -374,45 +370,45 @@ load_acl() {
network_get_device device "${interface}" network_get_device device "${interface}"
[ -z "${device}" ] && device="${interface}" [ -z "${device}" ] && device="${interface}"
_ipt_source="-i ${device} " _ipt_source="-i ${device} "
msg="源接口【${device}】," msg=$(i18n "Source iface [%s]," "${device}")
else else
msg="源接口【所有】," msg=$(i18n "Source iface [%s]," $(i18n "All"))
fi fi
if [ -n "$(echo ${i} | grep '^iprange:')" ]; then if [ -n "$(echo ${i} | grep '^iprange:')" ]; then
_iprange=$(echo ${i} | sed 's#iprange:##g') _iprange=$(echo ${i} | sed 's#iprange:##g')
_ipt_source=$(factor ${_iprange} "${_ipt_source}-m iprange --src-range") _ipt_source=$(factor ${_iprange} "${_ipt_source}-m iprange --src-range")
msg="${msg}IP range【${_iprange}】," msg="${msg}$(i18n "IP range [%s]," "${_iprange}")"
_ipv4="1" _ipv4="1"
unset _iprange unset _iprange
elif [ -n "$(echo ${i} | grep '^ipset:')" ]; then elif [ -n "$(echo ${i} | grep '^ipset:')" ]; then
_ipset=$(echo ${i} | sed 's#ipset:##g') _ipset=$(echo ${i} | sed 's#ipset:##g')
msg="${msg}IPset${_ipset}】," msg="${msg}IPset$(i18n "[%s]," "${_ipset}")"
ipset -q list ${_ipset} >/dev/null ipset -q list ${_ipset} >/dev/null
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
_ipt_source="${_ipt_source}-m set --match-set ${_ipset} src" _ipt_source="${_ipt_source}-m set --match-set ${_ipset} src"
unset _ipset unset _ipset
else else
echolog " - 【$remarks】,${msg}不存在,忽略。" log 2 "$(i18n "[%s]," "${remarks}")${msg}$(i18n "Does not exist, ignore.")"
unset _ipset unset _ipset
continue continue
fi fi
elif [ -n "$(echo ${i} | grep '^ip:')" ]; then elif [ -n "$(echo ${i} | grep '^ip:')" ]; then
_ip=$(echo ${i} | sed 's#ip:##g') _ip=$(echo ${i} | sed 's#ip:##g')
_ipt_source=$(factor ${_ip} "${_ipt_source}-s") _ipt_source=$(factor ${_ip} "${_ipt_source}-s")
msg="${msg}IP${_ip}】," msg="${msg}IP$(i18n "[%s]," "${_ip}")"
_ipv4="1" _ipv4="1"
unset _ip unset _ip
elif [ -n "$(echo ${i} | grep '^mac:')" ]; then elif [ -n "$(echo ${i} | grep '^mac:')" ]; then
_mac=$(echo ${i} | sed 's#mac:##g') _mac=$(echo ${i} | sed 's#mac:##g')
_ipt_source=$(factor ${_mac} "${_ipt_source}-m mac --mac-source") _ipt_source=$(factor ${_mac} "${_ipt_source}-m mac --mac-source")
msg="${msg}MAC${_mac}】," msg="${msg}MAC$(i18n "[%s]," "${_mac}")"
unset _mac unset _mac
elif [ -n "$(echo ${i} | grep '^any')" ]; then elif [ -n "$(echo ${i} | grep '^any')" ]; then
msg="${msg}所有设备," msg="${msg}$(i18n "All device,")"
else else
continue continue
fi fi
msg="$remarks】,${msg}" msg="$(i18n "[%s]," "${remarks}")${msg}"
ipt_tmp=$ipt_n ipt_tmp=$ipt_n
[ -n "${is_tproxy}" ] && ipt_tmp=$ipt_m [ -n "${is_tproxy}" ] && ipt_tmp=$ipt_m
@@ -421,11 +417,11 @@ load_acl() {
if ! has_1_65535 "$tcp_no_redir_ports"; then if ! has_1_65535 "$tcp_no_redir_ports"; then
[ "$_ipv4" != "1" ] && add_port_rules "$ip6t_m -A PSW2 $(comment "$remarks") ${_ipt_source} -p tcp" $tcp_no_redir_ports "-j RETURN" 2>/dev/null [ "$_ipv4" != "1" ] && add_port_rules "$ip6t_m -A PSW2 $(comment "$remarks") ${_ipt_source} -p tcp" $tcp_no_redir_ports "-j RETURN" 2>/dev/null
add_port_rules "$ipt_tmp -A PSW2 $(comment "$remarks") ${_ipt_source} -p tcp" $tcp_no_redir_ports "-j RETURN" add_port_rules "$ipt_tmp -A PSW2 $(comment "$remarks") ${_ipt_source} -p tcp" $tcp_no_redir_ports "-j RETURN"
echolog " - ${msg}不代理 TCP 端口[${tcp_no_redir_ports}]" log 2 "${msg}$(i18n "not proxy %s port [%s]" "TCP" "${tcp_no_redir_ports}")"
else else
#结束时会return无需加多余的规则。 # It will return when it ends, so no extra rules are needed.
tcp_proxy_mode="disable" tcp_proxy_mode="disable"
echolog " - ${msg}不代理所有 TCP" log 2 "${msg}$(i18n "not proxy all %s" "TCP")"
fi fi
} }
@@ -433,11 +429,11 @@ load_acl() {
if ! has_1_65535 "$udp_no_redir_ports"; then if ! has_1_65535 "$udp_no_redir_ports"; then
[ "$_ipv4" != "1" ] && add_port_rules "$ip6t_m -A PSW2 $(comment "$remarks") ${_ipt_source} -p udp" $udp_no_redir_ports "-j RETURN" 2>/dev/null [ "$_ipv4" != "1" ] && add_port_rules "$ip6t_m -A PSW2 $(comment "$remarks") ${_ipt_source} -p udp" $udp_no_redir_ports "-j RETURN" 2>/dev/null
add_port_rules "$ipt_m -A PSW2 $(comment "$remarks") ${_ipt_source} -p udp" $udp_no_redir_ports "-j RETURN" add_port_rules "$ipt_m -A PSW2 $(comment "$remarks") ${_ipt_source} -p udp" $udp_no_redir_ports "-j RETURN"
echolog " - ${msg}不代理 UDP 端口[${udp_no_redir_ports}]" log 2 "${msg}$(i18n "not proxy %s port [%s]" "UDP" "${udp_no_redir_ports}")"
else else
#结束时会return无需加多余的规则。 # It will return when it ends, so no extra rules are needed.
udp_proxy_mode="disable" udp_proxy_mode="disable"
echolog " - ${msg}不代理所有 UDP" log 2 "${msg}$(i18n "not proxy all %s" "UDP")"
fi fi
} }
@@ -446,7 +442,7 @@ load_acl() {
$ip6t_n -A PSW2_DNS $(comment "$remarks") -p udp ${_ipt_source} --dport 53 -j REDIRECT --to-ports $dns_redirect_port 2>/dev/null $ip6t_n -A PSW2_DNS $(comment "$remarks") -p udp ${_ipt_source} --dport 53 -j REDIRECT --to-ports $dns_redirect_port 2>/dev/null
$ipt_n -A PSW2_DNS $(comment "$remarks") -p tcp ${_ipt_source} --dport 53 -j REDIRECT --to-ports $dns_redirect_port $ipt_n -A PSW2_DNS $(comment "$remarks") -p tcp ${_ipt_source} --dport 53 -j REDIRECT --to-ports $dns_redirect_port
$ip6t_n -A PSW2_DNS $(comment "$remarks") -p tcp ${_ipt_source} --dport 53 -j REDIRECT --to-ports $dns_redirect_port 2>/dev/null $ip6t_n -A PSW2_DNS $(comment "$remarks") -p tcp ${_ipt_source} --dport 53 -j REDIRECT --to-ports $dns_redirect_port 2>/dev/null
[ -z "$(get_cache_var "ACL_${sid}_default")" ] && echolog " - ${msg}使用与全局配置不相同节点已将DNS强制重定向到专用 DNS 服务器。" [ -z "$(get_cache_var "ACL_${sid}_default")" ] && log 2 "${msg}$(i18n "Using a node that is different from the global configuration, DNS has been forcibly redirected to a dedicated DNS server.")"
else else
$ipt_n -A PSW2_DNS $(comment "$remarks") -p udp ${_ipt_source} --dport 53 -j RETURN $ipt_n -A PSW2_DNS $(comment "$remarks") -p udp ${_ipt_source} --dport 53 -j RETURN
$ip6t_n -A PSW2_DNS $(comment "$remarks") -p udp ${_ipt_source} --dport 53 -j RETURN 2>/dev/null $ip6t_n -A PSW2_DNS $(comment "$remarks") -p udp ${_ipt_source} --dport 53 -j RETURN 2>/dev/null
@@ -455,7 +451,7 @@ load_acl() {
fi fi
[ "$tcp_proxy_mode" != "disable" ] && [ -n "$redir_port" ] && { [ "$tcp_proxy_mode" != "disable" ] && [ -n "$redir_port" ] && {
msg2="${msg}使用 TCP 节点[$node_remark]" msg2="${msg}$(i18n "Use the %s node [%s]" "TCP" "${node_remark}")"
if [ -n "${is_tproxy}" ]; then if [ -n "${is_tproxy}" ]; then
msg2="${msg2}(TPROXY:${redir_port})" msg2="${msg2}(TPROXY:${redir_port})"
ipt_j="-j PSW2_RULE" ipt_j="-j PSW2_RULE"
@@ -487,13 +483,13 @@ load_acl() {
add_port_rules "$ip6t_m -A PSW2 $(comment "$remarks") -p tcp ${_ipt_source}" $tcp_redir_ports "-j PSW2_RULE" 2>/dev/null add_port_rules "$ip6t_m -A PSW2 $(comment "$remarks") -p tcp ${_ipt_source}" $tcp_redir_ports "-j PSW2_RULE" 2>/dev/null
$ip6t_m -A PSW2 $(comment "$remarks") -p tcp ${_ipt_source} $(REDIRECT $redir_port TPROXY) 2>/dev/null $ip6t_m -A PSW2 $(comment "$remarks") -p tcp ${_ipt_source} $(REDIRECT $redir_port TPROXY) 2>/dev/null
} }
echolog " - ${msg2}" log 2 "${msg2}"
} }
$ipt_tmp -A PSW2 $(comment "$remarks") ${_ipt_source} -p tcp -j RETURN $ipt_tmp -A PSW2 $(comment "$remarks") ${_ipt_source} -p tcp -j RETURN
[ "$_ipv4" != "1" ] && $ip6t_m -A PSW2 $(comment "$remarks") ${_ipt_source} -p tcp -j RETURN 2>/dev/null [ "$_ipv4" != "1" ] && $ip6t_m -A PSW2 $(comment "$remarks") ${_ipt_source} -p tcp -j RETURN 2>/dev/null
[ "$udp_proxy_mode" != "disable" ] && [ -n "$redir_port" ] && { [ "$udp_proxy_mode" != "disable" ] && [ -n "$redir_port" ] && {
msg2="${msg}使用 UDP 节点[$node_remark](TPROXY:${redir_port})" msg2="${msg}$(i18n "Use the %s node [%s]" "UDP" "${node_remark}")(TPROXY:${redir_port})"
$ipt_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source} -d $FAKE_IP -j PSW2_RULE $ipt_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source} -d $FAKE_IP -j PSW2_RULE
add_shunt_t_rule "${shunt_list4}" "$ipt_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source}" "-j PSW2_RULE" $udp_redir_ports add_shunt_t_rule "${shunt_list4}" "$ipt_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source}" "-j PSW2_RULE" $udp_redir_ports
@@ -506,7 +502,7 @@ load_acl() {
add_port_rules "$ip6t_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source}" $udp_redir_ports "-j PSW2_RULE" 2>/dev/null add_port_rules "$ip6t_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source}" $udp_redir_ports "-j PSW2_RULE" 2>/dev/null
$ip6t_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source} $(REDIRECT $redir_port TPROXY) 2>/dev/null $ip6t_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source} $(REDIRECT $redir_port TPROXY) 2>/dev/null
} }
echolog " - ${msg2}" log 2 "${msg2}"
} }
$ipt_m -A PSW2 $(comment "$remarks") ${_ipt_source} -p udp -j RETURN $ipt_m -A PSW2 $(comment "$remarks") ${_ipt_source} -p udp -j RETURN
[ "$_ipv4" != "1" ] && $ip6t_m -A PSW2 $(comment "$remarks") ${_ipt_source} -p udp -j RETURN 2>/dev/null [ "$_ipv4" != "1" ] && $ip6t_m -A PSW2 $(comment "$remarks") ${_ipt_source} -p udp -j RETURN 2>/dev/null
@@ -518,44 +514,44 @@ load_acl() {
} }
[ "$ENABLED_DEFAULT_ACL" == 1 ] && [ "$CLIENT_PROXY" == 1 ] && { [ "$ENABLED_DEFAULT_ACL" == 1 ] && [ "$CLIENT_PROXY" == 1 ] && {
# 加载默认代理模式 local comment_d="$(i18n "Default")"
msg="【默认】," msg="$(i18n "[%s]," ${comment_d})"
local ipt_tmp=$ipt_n local ipt_tmp=$ipt_n
[ -n "${is_tproxy}" ] && ipt_tmp=$ipt_m [ -n "${is_tproxy}" ] && ipt_tmp=$ipt_m
[ "$TCP_NO_REDIR_PORTS" != "disable" ] && { [ "$TCP_NO_REDIR_PORTS" != "disable" ] && {
add_port_rules "$ip6t_m -A PSW2 $(comment "默认") -p tcp" $TCP_NO_REDIR_PORTS "-j RETURN" add_port_rules "$ip6t_m -A PSW2 $(comment "${comment_d}") -p tcp" $TCP_NO_REDIR_PORTS "-j RETURN"
add_port_rules "$ipt_tmp -A PSW2 $(comment "默认") -p tcp" $TCP_NO_REDIR_PORTS "-j RETURN" add_port_rules "$ipt_tmp -A PSW2 $(comment "${comment_d}") -p tcp" $TCP_NO_REDIR_PORTS "-j RETURN"
if ! has_1_65535 "$TCP_NO_REDIR_PORTS"; then if ! has_1_65535 "$TCP_NO_REDIR_PORTS"; then
echolog " - ${msg}不代理 TCP 端口[${TCP_NO_REDIR_PORTS}]" log 2 "${msg}$(i18n "not proxy %s port [%s]" "TCP" "${TCP_NO_REDIR_PORTS}")"
else else
TCP_PROXY_MODE="disable" TCP_PROXY_MODE="disable"
echolog " - ${msg}不代理所有 TCP 端口" log 2 "${msg}$(i18n "not proxy all %s" "TCP")"
fi fi
} }
[ "$UDP_NO_REDIR_PORTS" != "disable" ] && { [ "$UDP_NO_REDIR_PORTS" != "disable" ] && {
add_port_rules "$ip6t_m -A PSW2 $(comment "默认") -p udp" $UDP_NO_REDIR_PORTS "-j RETURN" add_port_rules "$ip6t_m -A PSW2 $(comment "${comment_d}") -p udp" $UDP_NO_REDIR_PORTS "-j RETURN"
add_port_rules "$ipt_tmp -A PSW2 $(comment "默认") -p udp" $UDP_NO_REDIR_PORTS "-j RETURN" add_port_rules "$ipt_tmp -A PSW2 $(comment "${comment_d}") -p udp" $UDP_NO_REDIR_PORTS "-j RETURN"
if ! has_1_65535 "$UDP_NO_REDIR_PORTS"; then if ! has_1_65535 "$UDP_NO_REDIR_PORTS"; then
echolog " - ${msg}不代理 UDP 端口[${UDP_NO_REDIR_PORTS}]" log 2 "${msg}$(i18n "not proxy %s port [%s]" "UDP" "${UDP_NO_REDIR_PORTS}")"
else else
UDP_PROXY_MODE="disable" UDP_PROXY_MODE="disable"
echolog " - ${msg}不代理所有 UDP 端口" log 2 "${msg}$(i18n "not proxy all %s" "UDP")"
fi fi
} }
if ([ "$TCP_PROXY_MODE" != "disable" ] || [ "$UDP_PROXY_MODE" != "disable" ]) && [ -n "$NODE" ]; then if ([ "$TCP_PROXY_MODE" != "disable" ] || [ "$UDP_PROXY_MODE" != "disable" ]) && [ -n "$NODE" ]; then
[ -n "$DNS_REDIRECT_PORT" ] && { [ -n "$DNS_REDIRECT_PORT" ] && {
$ipt_n -A PSW2_DNS $(comment "默认") -p udp --dport 53 -j REDIRECT --to-ports $DNS_REDIRECT_PORT $ipt_n -A PSW2_DNS $(comment "${comment_d}") -p udp --dport 53 -j REDIRECT --to-ports $DNS_REDIRECT_PORT
$ip6t_n -A PSW2_DNS $(comment "默认") -p udp --dport 53 -j REDIRECT --to-ports $DNS_REDIRECT_PORT 2>/dev/null $ip6t_n -A PSW2_DNS $(comment "${comment_d}") -p udp --dport 53 -j REDIRECT --to-ports $DNS_REDIRECT_PORT 2>/dev/null
$ipt_n -A PSW2_DNS $(comment "默认") -p tcp --dport 53 -j REDIRECT --to-ports $DNS_REDIRECT_PORT $ipt_n -A PSW2_DNS $(comment "${comment_d}") -p tcp --dport 53 -j REDIRECT --to-ports $DNS_REDIRECT_PORT
$ip6t_n -A PSW2_DNS $(comment "默认") -p tcp --dport 53 -j REDIRECT --to-ports $DNS_REDIRECT_PORT 2>/dev/null $ip6t_n -A PSW2_DNS $(comment "${comment_d}") -p tcp --dport 53 -j REDIRECT --to-ports $DNS_REDIRECT_PORT 2>/dev/null
} }
fi fi
if [ "$TCP_PROXY_MODE" != "disable" ] && [ -n "$NODE" ]; then if [ "$TCP_PROXY_MODE" != "disable" ] && [ -n "$NODE" ]; then
msg2="${msg}使用 TCP 节点[$(config_n_get $NODE remarks)]" msg2="${msg}$(i18n "Use the %s node [%s]" "TCP" "$(config_n_get $NODE remarks)")"
if [ -n "${is_tproxy}" ]; then if [ -n "${is_tproxy}" ]; then
msg2="${msg2}(TPROXY:${REDIR_PORT})" msg2="${msg2}(TPROXY:${REDIR_PORT})"
ipt_j="-j PSW2_RULE" ipt_j="-j PSW2_RULE"
@@ -565,48 +561,48 @@ load_acl() {
fi fi
[ "$accept_icmp" = "1" ] && { [ "$accept_icmp" = "1" ] && {
$ipt_n -A PSW2 $(comment "默认") -p icmp -d $FAKE_IP $(REDIRECT) $ipt_n -A PSW2 $(comment "${comment_d}") -p icmp -d $FAKE_IP $(REDIRECT)
add_shunt_t_rule "${SHUNT_LIST4}" "$ipt_n -A PSW2 $(comment "默认") -p icmp" "$(REDIRECT)" add_shunt_t_rule "${SHUNT_LIST4}" "$ipt_n -A PSW2 $(comment "${comment_d}") -p icmp" "$(REDIRECT)"
$ipt_n -A PSW2 $(comment "默认") -p icmp $(REDIRECT) $ipt_n -A PSW2 $(comment "${comment_d}") -p icmp $(REDIRECT)
} }
[ "$accept_icmpv6" = "1" ] && [ "$PROXY_IPV6" == "1" ] && { [ "$accept_icmpv6" = "1" ] && [ "$PROXY_IPV6" == "1" ] && {
$ip6t_n -A PSW2 $(comment "默认") -p ipv6-icmp -d $FAKE_IP_6 $(REDIRECT) $ip6t_n -A PSW2 $(comment "${comment_d}") -p ipv6-icmp -d $FAKE_IP_6 $(REDIRECT)
add_shunt_t_rule "${SHUNT_LIST6}" "$ip6t_n -A PSW2 $(comment "默认") -p ipv6-icmp" "$(REDIRECT)" add_shunt_t_rule "${SHUNT_LIST6}" "$ip6t_n -A PSW2 $(comment "${comment_d}") -p ipv6-icmp" "$(REDIRECT)"
$ip6t_n -A PSW2 $(comment "默认") -p ipv6-icmp $(REDIRECT) $ip6t_n -A PSW2 $(comment "${comment_d}") -p ipv6-icmp $(REDIRECT)
} }
$ipt_tmp -A PSW2 $(comment "默认") -p tcp -d $FAKE_IP ${ipt_j} $ipt_tmp -A PSW2 $(comment "${comment_d}") -p tcp -d $FAKE_IP ${ipt_j}
add_shunt_t_rule "${SHUNT_LIST4}" "$ipt_tmp -A PSW2 $(comment "默认") -p tcp" "${ipt_j}" $TCP_REDIR_PORTS add_shunt_t_rule "${SHUNT_LIST4}" "$ipt_tmp -A PSW2 $(comment "${comment_d}") -p tcp" "${ipt_j}" $TCP_REDIR_PORTS
add_port_rules "$ipt_tmp -A PSW2 $(comment "默认") -p tcp" $TCP_REDIR_PORTS "${ipt_j}" add_port_rules "$ipt_tmp -A PSW2 $(comment "${comment_d}") -p tcp" $TCP_REDIR_PORTS "${ipt_j}"
[ -n "${is_tproxy}" ] && $ipt_m -A PSW2 $(comment "默认") -p tcp $(REDIRECT $REDIR_PORT TPROXY) [ -n "${is_tproxy}" ] && $ipt_m -A PSW2 $(comment "${comment_d}") -p tcp $(REDIRECT $REDIR_PORT TPROXY)
[ "$PROXY_IPV6" == "1" ] && { [ "$PROXY_IPV6" == "1" ] && {
$ip6t_m -A PSW2 $(comment "默认") -p tcp -d $FAKE_IP_6 -j PSW2_RULE $ip6t_m -A PSW2 $(comment "${comment_d}") -p tcp -d $FAKE_IP_6 -j PSW2_RULE
add_shunt_t_rule "${SHUNT_LIST6}" "$ip6t_m -A PSW2 $(comment "默认") -p tcp" "-j PSW2_RULE" $TCP_REDIR_PORTS add_shunt_t_rule "${SHUNT_LIST6}" "$ip6t_m -A PSW2 $(comment "${comment_d}") -p tcp" "-j PSW2_RULE" $TCP_REDIR_PORTS
add_port_rules "$ip6t_m -A PSW2 $(comment "默认") -p tcp" $TCP_REDIR_PORTS "-j PSW2_RULE" add_port_rules "$ip6t_m -A PSW2 $(comment "${comment_d}") -p tcp" $TCP_REDIR_PORTS "-j PSW2_RULE"
$ip6t_m -A PSW2 $(comment "默认") -p tcp $(REDIRECT $REDIR_PORT TPROXY) $ip6t_m -A PSW2 $(comment "${comment_d}") -p tcp $(REDIRECT $REDIR_PORT TPROXY)
} }
echolog "${msg2}" log 2 "${msg2}"
fi fi
if [ "$UDP_PROXY_MODE" != "disable" ] && [ -n "$NODE" ]; then if [ "$UDP_PROXY_MODE" != "disable" ] && [ -n "$NODE" ]; then
msg2="${msg}使用 UDP 节点[$(config_n_get $NODE remarks)](TPROXY:${REDIR_PORT})" msg2="${msg}$(i18n "Use the %s node [%s]" "UDP" "$(config_n_get $NODE remarks)")(TPROXY:${REDIR_PORT})"
$ipt_m -A PSW2 $(comment "默认") -p udp -d $FAKE_IP -j PSW2_RULE $ipt_m -A PSW2 $(comment "${comment_d}") -p udp -d $FAKE_IP -j PSW2_RULE
add_shunt_t_rule "${SHUNT_LIST4}" "$ipt_m -A PSW2 $(comment "默认") -p udp" "-j PSW2_RULE" $UDP_REDIR_PORTS add_shunt_t_rule "${SHUNT_LIST4}" "$ipt_m -A PSW2 $(comment "${comment_d}") -p udp" "-j PSW2_RULE" $UDP_REDIR_PORTS
add_port_rules "$ipt_m -A PSW2 $(comment "默认") -p udp" $UDP_REDIR_PORTS "-j PSW2_RULE" add_port_rules "$ipt_m -A PSW2 $(comment "${comment_d}") -p udp" $UDP_REDIR_PORTS "-j PSW2_RULE"
$ipt_m -A PSW2 $(comment "默认") -p udp $(REDIRECT $REDIR_PORT TPROXY) $ipt_m -A PSW2 $(comment "${comment_d}") -p udp $(REDIRECT $REDIR_PORT TPROXY)
[ "$PROXY_IPV6" == "1" ] && { [ "$PROXY_IPV6" == "1" ] && {
$ip6t_m -A PSW2 $(comment "默认") -p udp -d $FAKE_IP_6 -j PSW2_RULE $ip6t_m -A PSW2 $(comment "${comment_d}") -p udp -d $FAKE_IP_6 -j PSW2_RULE
add_shunt_t_rule "${SHUNT_LIST6}" "$ip6t_m -A PSW2 $(comment "默认") -p udp" "-j PSW2_RULE" $UDP_REDIR_PORTS add_shunt_t_rule "${SHUNT_LIST6}" "$ip6t_m -A PSW2 $(comment "${comment_d}") -p udp" "-j PSW2_RULE" $UDP_REDIR_PORTS
add_port_rules "$ip6t_m -A PSW2 $(comment "默认") -p udp" $UDP_REDIR_PORTS "-j PSW2_RULE" add_port_rules "$ip6t_m -A PSW2 $(comment "${comment_d}") -p udp" $UDP_REDIR_PORTS "-j PSW2_RULE"
$ip6t_m -A PSW2 $(comment "默认") -p udp $(REDIRECT $REDIR_PORT TPROXY) $ip6t_m -A PSW2 $(comment "${comment_d}") -p udp $(REDIRECT $REDIR_PORT TPROXY)
} }
echolog "${msg2}" log 2 "${msg2}"
fi fi
} }
} }
@@ -616,14 +612,14 @@ filter_haproxy() {
local ip=$(get_host_ip ipv4 $(echo $item | awk -F ":" '{print $1}') 1) local ip=$(get_host_ip ipv4 $(echo $item | awk -F ":" '{print $1}') 1)
[ -n "$ip" ] && ipset -q add $IPSET_VPS $ip [ -n "$ip" ] && ipset -q add $IPSET_VPS $ip
done done
echolog "加入负载均衡的节点到ipset[$IPSET_VPS]直连完成" log_i18n 1 "Add node to the load balancer is directly connected to %s[%s]." "ipset" "${IPSET_VPS}"
} }
filter_vpsip() { filter_vpsip() {
uci show $CONFIG | grep -E "(.address=|.download_address=)" | cut -d "'" -f 2 | grep -E "([0-9]{1,3}[\.]){3}[0-9]{1,3}" | grep -v "^127\.0\.0\.1$" | sed -e "/^$/d" | sed -e "s/^/add $IPSET_VPS &/g" | awk '{print $0} END{print "COMMIT"}' | ipset -! -R uci show $CONFIG | grep -E "(.address=|.download_address=)" | cut -d "'" -f 2 | grep -E "([0-9]{1,3}[\.]){3}[0-9]{1,3}" | grep -v "^127\.0\.0\.1$" | sed -e "/^$/d" | sed -e "s/^/add $IPSET_VPS &/g" | awk '{print $0} END{print "COMMIT"}' | ipset -! -R
echolog " - [$?]加入所有IPv4节点到ipset[$IPSET_VPS]直连完成" #log 1 "$(i18n "Add all %s nodes to %s[%s] direct connection complete." "IPv4" "ipset" "${$IPSET_VPS}")"
uci show $CONFIG | grep -E "(.address=|.download_address=)" | cut -d "'" -f 2 | grep -E "([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4}" | sed -e "/^$/d" | sed -e "s/^/add $IPSET_VPS6 &/g" | awk '{print $0} END{print "COMMIT"}' | ipset -! -R uci show $CONFIG | grep -E "(.address=|.download_address=)" | cut -d "'" -f 2 | grep -E "([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4}" | sed -e "/^$/d" | sed -e "s/^/add $IPSET_VPS6 &/g" | awk '{print $0} END{print "COMMIT"}' | ipset -! -R
echolog " - [$?]加入所有IPv6节点到ipset[$IPSET_VPS6]直连完成" #log 1 "$(i18n "Add all %s nodes to %s[%s] direct connection complete." "IPv6" "ipset" "${$IPSET_VPS6}")"
} }
filter_server_port() { filter_server_port() {
@@ -671,7 +667,8 @@ filter_direct_node_list() {
} }
add_firewall_rule() { add_firewall_rule() {
echolog "开始加载 iptables 防火墙规则..." log_i18n 0 "Starting to load %s firewall rules..." "iptables"
ipset -! create $IPSET_LOCAL nethash maxelem 1048576 ipset -! create $IPSET_LOCAL nethash maxelem 1048576
ipset -! create $IPSET_LAN nethash maxelem 1048576 ipset -! create $IPSET_LAN nethash maxelem 1048576
ipset -! create $IPSET_VPS nethash maxelem 1048576 ipset -! create $IPSET_VPS nethash maxelem 1048576
@@ -695,14 +692,14 @@ add_firewall_rule() {
$(gen_lanlist_6 | sed -e "s/^/add $IPSET_LAN6 /") $(gen_lanlist_6 | sed -e "s/^/add $IPSET_LAN6 /")
EOF EOF
# 忽略特殊IP段 # Ignore special IP ranges
local lan_ifname lan_ip local lan_ifname lan_ip
lan_ifname=$(uci -q -p /tmp/state get network.lan.ifname) lan_ifname=$(uci -q -p /tmp/state get network.lan.ifname)
[ -n "$lan_ifname" ] && { [ -n "$lan_ifname" ] && {
lan_ip=$(ip address show $lan_ifname | grep -w "inet" | awk '{print $2}') lan_ip=$(ip address show $lan_ifname | grep -w "inet" | awk '{print $2}')
lan_ip6=$(ip address show $lan_ifname | grep -w "inet6" | awk '{print $2}') lan_ip6=$(ip address show $lan_ifname | grep -w "inet6" | awk '{print $2}')
#echolog "本机IPv4网段互访直连${lan_ip}" #log_i18n 1 "local network segments (%s) direct connection: %s" "IPv4" "${lan_ip}"
#echolog "本机IPv6网段互访直连${lan_ip6}" #log_i18n 1 "local network segments (%s) direct connection: %s" "IPv6" "${lan_ip6}"
[ -n "$lan_ip" ] && ipset -! -R <<-EOF [ -n "$lan_ip" ] && ipset -! -R <<-EOF
$(echo $lan_ip | sed -e "s/ /\n/g" | sed -e "s/^/add $IPSET_LAN /") $(echo $lan_ip | sed -e "s/ /\n/g" | sed -e "s/^/add $IPSET_LAN /")
@@ -714,18 +711,16 @@ add_firewall_rule() {
} }
[ -n "$ISP_DNS" ] && { [ -n "$ISP_DNS" ] && {
#echolog "处理 ISP DNS 例外..."
for ispip in $ISP_DNS; do for ispip in $ISP_DNS; do
ipset -! add $IPSET_LAN $ispip ipset -! add $IPSET_LAN $ispip
echolog " - [$?]追加ISP IPv4 DNS到白名单${ispip}" log_i18n 1 "$(i18n "Add ISP %s DNS to the whitelist: %s" "IPv4" "${ispip}")"
done done
} }
[ -n "$ISP_DNS6" ] && { [ -n "$ISP_DNS6" ] && {
#echolog "处理 ISP IPv6 DNS 例外..."
for ispip6 in $ISP_DNS6; do for ispip6 in $ISP_DNS6; do
ipset -! add $IPSET_LAN6 $ispip6 ipset -! add $IPSET_LAN6 $ispip6
echolog " - [$?]追加ISP IPv6 DNS到白名单${ispip6}" log_i18n 1 "$(i18n "Add ISP %s DNS to the whitelist: %s" "IPv6" "${ispip6}")"
done done
} }
@@ -734,10 +729,11 @@ add_firewall_rule() {
ipset -! create $ipset_global_white nethash maxelem 1048576 timeout 259200 ipset -! create $ipset_global_white nethash maxelem 1048576 timeout 259200
ipset -! create $ipset_global_white6 nethash family inet6 maxelem 1048576 timeout 259200 ipset -! create $ipset_global_white6 nethash family inet6 maxelem 1048576 timeout 259200
#分流规则的IP列表(使用分流节点时导入)
# Shunt rules IP list (import when use shunt node)
gen_shunt_list "${NODE}" SHUNT_LIST4 SHUNT_LIST6 ${WRITE_IPSET_DIRECT} ${ipset_global_white} ${ipset_global_white6} gen_shunt_list "${NODE}" SHUNT_LIST4 SHUNT_LIST6 ${WRITE_IPSET_DIRECT} ${ipset_global_white} ${ipset_global_white6}
# 过滤所有节点IP # Filter all node IPs
filter_vpsip > /dev/null 2>&1 & filter_vpsip > /dev/null 2>&1 &
filter_haproxy > /dev/null 2>&1 & filter_haproxy > /dev/null 2>&1 &
@@ -799,7 +795,7 @@ add_firewall_rule() {
local dns_address=$(echo $auto_dns | awk -F '#' '{print $1}') local dns_address=$(echo $auto_dns | awk -F '#' '{print $1}')
local dns_port=$(echo $auto_dns | awk -F '#' '{print $2}') local dns_port=$(echo $auto_dns | awk -F '#' '{print $2}')
$ipt_m -A PSW2_OUTPUT -p udp -d ${dns_address} --dport ${dns_port:-53} -j RETURN $ipt_m -A PSW2_OUTPUT -p udp -d ${dns_address} --dport ${dns_port:-53} -j RETURN
echolog " - [$?]追加直连DNS到iptables${dns_address}:${dns_port:-53}" log_i18n 1 "$(i18n "Add direct DNS to %s: %s" "iptables" "${dns_address}:${dns_port:-53}")"
done done
} }
$ipt_m -A PSW2_OUTPUT -m mark --mark 0xff -j RETURN $ipt_m -A PSW2_OUTPUT -m mark --mark 0xff -j RETURN
@@ -861,15 +857,15 @@ add_firewall_rule() {
TCP_LOCALHOST_PROXY=$LOCALHOST_PROXY TCP_LOCALHOST_PROXY=$LOCALHOST_PROXY
UDP_LOCALHOST_PROXY=$LOCALHOST_PROXY UDP_LOCALHOST_PROXY=$LOCALHOST_PROXY
msg="【路由器本机】," msg="$(i18n "[Local],")"
[ "$TCP_NO_REDIR_PORTS" != "disable" ] && { [ "$TCP_NO_REDIR_PORTS" != "disable" ] && {
add_port_rules "$ipt_tmp -A PSW2_OUTPUT -p tcp" $TCP_NO_REDIR_PORTS "-j RETURN" add_port_rules "$ipt_tmp -A PSW2_OUTPUT -p tcp" $TCP_NO_REDIR_PORTS "-j RETURN"
add_port_rules "$ip6t_m -A PSW2_OUTPUT -p tcp" $TCP_NO_REDIR_PORTS "-j RETURN" add_port_rules "$ip6t_m -A PSW2_OUTPUT -p tcp" $TCP_NO_REDIR_PORTS "-j RETURN"
if ! has_1_65535 "$TCP_NO_REDIR_PORTS"; then if ! has_1_65535 "$TCP_NO_REDIR_PORTS"; then
echolog " - ${msg}不代理 TCP 端口[${TCP_NO_REDIR_PORTS}]" log 1 "${msg}$(i18n "not proxy %s port [%s]" "TCP" "${TCP_NO_REDIR_PORTS}")"
else else
unset TCP_LOCALHOST_PROXY unset TCP_LOCALHOST_PROXY
echolog " - ${msg}不代理所有 TCP" log 1 "${msg}$(i18n "not proxy all %s" "TCP")"
fi fi
} }
@@ -877,10 +873,10 @@ add_firewall_rule() {
add_port_rules "$ipt_m -A PSW2_OUTPUT -p udp" $UDP_NO_REDIR_PORTS "-j RETURN" add_port_rules "$ipt_m -A PSW2_OUTPUT -p udp" $UDP_NO_REDIR_PORTS "-j RETURN"
add_port_rules "$ip6t_m -A PSW2_OUTPUT -p udp" $UDP_NO_REDIR_PORTS "-j RETURN" add_port_rules "$ip6t_m -A PSW2_OUTPUT -p udp" $UDP_NO_REDIR_PORTS "-j RETURN"
if ! has_1_65535 "$UDP_NO_REDIR_PORTS"; then if ! has_1_65535 "$UDP_NO_REDIR_PORTS"; then
echolog " - ${msg}不代理 UDP 端口[${UDP_NO_REDIR_PORTS}]" log 1 "${msg}$(i18n "not proxy %s port [%s]" "UDP" "${UDP_NO_REDIR_PORTS}")"
else else
unset UDP_LOCALHOST_PROXY unset UDP_LOCALHOST_PROXY
echolog " - ${msg}不代理所有 UDP" log 1 "${msg}$(i18n "not proxy all %s" "UDP")"
fi fi
} }
@@ -893,7 +889,9 @@ add_firewall_rule() {
} }
fi fi
# 加载路由器自身代理 TCP local comment_l="$(i18n "Local")"
# Loading local router proxy TCP
if [ -n "$NODE" ] && [ "$TCP_LOCALHOST_PROXY" = "1" ]; then if [ -n "$NODE" ] && [ "$TCP_LOCALHOST_PROXY" = "1" ]; then
[ "$accept_icmp" = "1" ] && { [ "$accept_icmp" = "1" ] && {
$ipt_n -A OUTPUT -p icmp -j PSW2_OUTPUT $ipt_n -A OUTPUT -p icmp -j PSW2_OUTPUT
@@ -920,8 +918,8 @@ add_firewall_rule() {
add_port_rules "$ipt_tmp -A PSW2_OUTPUT -p tcp" $TCP_REDIR_PORTS "${ipt_j}" 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 -p tcp -j PSW2_OUTPUT
[ -n "${is_tproxy}" ] && { [ -n "${is_tproxy}" ] && {
$ipt_m -A PSW2 $(comment "本机") -p tcp -i lo $(REDIRECT $REDIR_PORT TPROXY) $ipt_m -A PSW2 $(comment "${comment_l}") -p tcp -i lo $(REDIRECT $REDIR_PORT TPROXY)
$ipt_m -A PSW2 $(comment "本机") -p tcp -i lo -j RETURN $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) -p tcp -j PSW2_OUTPUT"
} }
@@ -929,8 +927,8 @@ add_firewall_rule() {
$ip6t_m -A PSW2_OUTPUT -p tcp -d $FAKE_IP_6 -j PSW2_RULE $ip6t_m -A PSW2_OUTPUT -p tcp -d $FAKE_IP_6 -j PSW2_RULE
add_shunt_t_rule "${SHUNT_LIST6}" "$ip6t_m -A PSW2_OUTPUT -p tcp" "-j PSW2_RULE" $TCP_REDIR_PORTS add_shunt_t_rule "${SHUNT_LIST6}" "$ip6t_m -A PSW2_OUTPUT -p tcp" "-j PSW2_RULE" $TCP_REDIR_PORTS
add_port_rules "$ip6t_m -A PSW2_OUTPUT -p tcp" $TCP_REDIR_PORTS "-j PSW2_RULE" add_port_rules "$ip6t_m -A PSW2_OUTPUT -p tcp" $TCP_REDIR_PORTS "-j PSW2_RULE"
$ip6t_m -A PSW2 $(comment "本机") -p tcp -i lo $(REDIRECT $REDIR_PORT TPROXY) $ip6t_m -A PSW2 $(comment "${comment_l}") -p tcp -i lo $(REDIRECT $REDIR_PORT TPROXY)
$ip6t_m -A PSW2 $(comment "本机") -p tcp -i lo -j RETURN $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) -p tcp -j PSW2_OUTPUT"
} }
@@ -942,21 +940,21 @@ add_firewall_rule() {
} }
fi fi
# 加载路由器自身代理 UDP # Loading local router proxy UDP
if [ -n "$NODE" ] && [ "$UDP_LOCALHOST_PROXY" = "1" ]; then if [ -n "$NODE" ] && [ "$UDP_LOCALHOST_PROXY" = "1" ]; then
$ipt_m -A PSW2_OUTPUT -p udp -d $FAKE_IP -j PSW2_RULE $ipt_m -A PSW2_OUTPUT -p udp -d $FAKE_IP -j PSW2_RULE
add_shunt_t_rule "${SHUNT_LIST4}" "$ipt_m -A PSW2_OUTPUT -p udp" "-j PSW2_RULE" $UDP_REDIR_PORTS add_shunt_t_rule "${SHUNT_LIST4}" "$ipt_m -A PSW2_OUTPUT -p udp" "-j PSW2_RULE" $UDP_REDIR_PORTS
add_port_rules "$ipt_m -A PSW2_OUTPUT -p udp" $UDP_REDIR_PORTS "-j PSW2_RULE" add_port_rules "$ipt_m -A PSW2_OUTPUT -p udp" $UDP_REDIR_PORTS "-j PSW2_RULE"
$ipt_m -A PSW2 $(comment "本机") -p udp -i lo $(REDIRECT $REDIR_PORT TPROXY) $ipt_m -A PSW2 $(comment "${comment_l}") -p udp -i lo $(REDIRECT $REDIR_PORT TPROXY)
$ipt_m -A PSW2 $(comment "本机") -p udp -i lo -j RETURN $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) -p udp -j PSW2_OUTPUT"
[ "$PROXY_IPV6" == "1" ] && { [ "$PROXY_IPV6" == "1" ] && {
$ip6t_m -A PSW2_OUTPUT -p udp -d $FAKE_IP_6 -j PSW2_RULE $ip6t_m -A PSW2_OUTPUT -p udp -d $FAKE_IP_6 -j PSW2_RULE
add_shunt_t_rule "${SHUNT_LIST6}" "$ip6t_m -A PSW2_OUTPUT -p udp" "-j PSW2_RULE" $UDP_REDIR_PORTS add_shunt_t_rule "${SHUNT_LIST6}" "$ip6t_m -A PSW2_OUTPUT -p udp" "-j PSW2_RULE" $UDP_REDIR_PORTS
add_port_rules "$ip6t_m -A PSW2_OUTPUT -p udp" $UDP_REDIR_PORTS "-j PSW2_RULE" add_port_rules "$ip6t_m -A PSW2_OUTPUT -p udp" $UDP_REDIR_PORTS "-j PSW2_RULE"
$ip6t_m -A PSW2 $(comment "本机") -p udp -i lo $(REDIRECT $REDIR_PORT TPROXY) $ip6t_m -A PSW2 $(comment "${comment_l}") -p udp -i lo $(REDIRECT $REDIR_PORT TPROXY)
$ip6t_m -A PSW2 $(comment "本机") -p udp -i lo -j RETURN $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) -p udp -j PSW2_OUTPUT"
} }
@@ -978,12 +976,11 @@ add_firewall_rule() {
$ip6t_m -A PSW2 -p udp --dport 53 -j RETURN $ip6t_m -A PSW2 -p udp --dport 53 -j RETURN
} }
# 加载ACLS
load_acl load_acl
filter_direct_node_list > /dev/null 2>&1 & filter_direct_node_list > /dev/null 2>&1 &
echolog "防火墙规则加载完成!" log_i18n 0 "%s firewall rules load complete!" "iptables"
} }
del_firewall_rule() { del_firewall_rule() {
@@ -1006,11 +1003,11 @@ del_firewall_rule() {
ip -6 rule del fwmark 1 table 100 2>/dev/null ip -6 rule del fwmark 1 table 100 2>/dev/null
ip -6 route del local ::/0 dev lo table 100 2>/dev/null ip -6 route del local ::/0 dev lo table 100 2>/dev/null
$DIR/app.sh echolog "删除 iptables 规则完成。" $DIR/app.sh log_i18n 0 "Delete %s rules is complete." "iptables"
} }
flush_ipset() { flush_ipset() {
$DIR/app.sh echolog "清空 IPSet" $DIR/app.sh log_i18n 0 "Clear %s." "IPSet"
for _name in $(ipset list | grep "Name: " | grep "passwall2_" | awk '{print $2}'); do for _name in $(ipset list | grep "Name: " | grep "passwall2_" | awk '{print $2}'); do
destroy_ipset ${_name} destroy_ipset ${_name}
done done

View File

@@ -36,7 +36,7 @@ while [ "$ENABLED" -eq 1 ]; do
[ -n "$(echo $cmd_check | grep "dns2socks")" ] && cmd_check=$(echo $cmd_check | sed "s#:# #g") [ -n "$(echo $cmd_check | grep "dns2socks")" ] && cmd_check=$(echo $cmd_check | sed "s#:# #g")
icount=$(pgrep -f "$(echo $cmd_check)" | wc -l) icount=$(pgrep -f "$(echo $cmd_check)" | wc -l)
if [ $icount = 0 ]; then if [ $icount = 0 ]; then
#echo "${cmd} 进程挂掉,重启" >> /tmp/log/passwall2.log #echo "${cmd} crashed, restarting." >> /tmp/log/passwall2.log
eval $(echo "nohup ${cmd} 2>&1 &") >/dev/null 2>&1 & eval $(echo "nohup ${cmd} 2>&1 &") >/dev/null 2>&1 &
fi fi
done done

View File

@@ -89,7 +89,7 @@ insert_rule_after() {
RULE_LAST_INDEX() { RULE_LAST_INDEX() {
[ $# -ge 3 ] || { [ $# -ge 3 ] || {
echolog "索引列举方式不正确(nftables),终止执行!" log_i18n 1 "Incorrect index listing method (%s), execution terminated!" "nftables"
return 1 return 1
} }
local table_name="${1}"; shift local table_name="${1}"; shift
@@ -196,10 +196,6 @@ gen_nftset() {
[ -n "${1}" ] && insert_nftset $nftset_name $timeout_argument_element $@ [ -n "${1}" ] && insert_nftset $nftset_name $timeout_argument_element $@
} }
get_action_chain_name() {
echo "全局代理"
}
gen_lanlist() { gen_lanlist() {
cat <<-EOF cat <<-EOF
0.0.0.0/8 0.0.0.0/8
@@ -302,7 +298,7 @@ gen_shunt_list() {
insert_nftset $nftset_v4 "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_v4 "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_v6 "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_v6 "0" $(get_geoip $_geoip_code ipv6 | grep -E "([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4}")
fi fi
echolog " - [$?]解析分流规则[$shunt_id]-[geoip:${_geoip_code}]加入到 NFTSET 完成" log 1 "$(i18n "parse the traffic splitting rules[%s]-[geoip:%s] add to %s to complete." "${shunt_id}" "${_geoip_code}" "NFTSET")"
} }
} }
} }
@@ -346,7 +342,7 @@ add_shunt_t_rule() {
load_acl() { load_acl() {
[ "$ENABLED_ACLS" == 1 ] && { [ "$ENABLED_ACLS" == 1 ] && {
echolog "访问控制:" log_i18n 1 "Access Control:"
acl_app acl_app
for sid in $(ls -F ${TMP_ACL_PATH} | grep '/$' | awk -F '/' '{print $1}' | grep -v 'default'); do for sid in $(ls -F ${TMP_ACL_PATH} | grep '/$' | awk -F '/' '{print $1}' | grep -v 'default'); do
eval $(uci -q show "${CONFIG}.${sid}" | cut -d'.' -sf 3-) eval $(uci -q show "${CONFIG}.${sid}" | cut -d'.' -sf 3-)
@@ -381,7 +377,7 @@ load_acl() {
gen_nftset $nftset_white ipv4_addr 3d 3d gen_nftset $nftset_white ipv4_addr 3d 3d
gen_nftset $nftset_white6 ipv6_addr 3d 3d gen_nftset $nftset_white6 ipv6_addr 3d 3d
#分流规则的IP列表(使用分流节点时导入) # Shunt rules IP list (import when use shunt node)
gen_shunt_list "${node}" shunt_list4 shunt_list6 ${write_ipset_direct} ${nftset_white} ${nftset_white6} gen_shunt_list "${node}" shunt_list4 shunt_list6 ${write_ipset_direct} ${nftset_white} ${nftset_white6}
fi fi
} }
@@ -398,48 +394,48 @@ load_acl() {
network_get_device device "${interface}" network_get_device device "${interface}"
[ -z "${device}" ] && device="${interface}" [ -z "${device}" ] && device="${interface}"
_ipt_source="iifname ${device} " _ipt_source="iifname ${device} "
msg="源接口【${device}】," msg=$(i18n "Source iface [%s]," "${device}")
else else
msg="源接口【所有】," msg=$(i18n "Source iface [%s]," $(i18n "All"))
fi fi
if [ -n "$(echo ${i} | grep '^iprange:')" ]; then if [ -n "$(echo ${i} | grep '^iprange:')" ]; then
_iprange=$(echo ${i} | sed 's#iprange:##g') _iprange=$(echo ${i} | sed 's#iprange:##g')
_ipt_source=$(factor ${_iprange} "${_ipt_source}ip saddr") _ipt_source=$(factor ${_iprange} "${_ipt_source}ip saddr")
msg="${msg}IP range【${_iprange}】," msg="${msg}$(i18n "IP range [%s]," "${_iprange}")"
_ipv4="1" _ipv4="1"
unset _iprange unset _iprange
elif [ -n "$(echo ${i} | grep '^ipset:')" ]; then elif [ -n "$(echo ${i} | grep '^ipset:')" ]; then
_ipset=$(echo ${i} | sed 's#ipset:##g') _ipset=$(echo ${i} | sed 's#ipset:##g')
_ipt_source="${_ipt_source}ip daddr @${_ipset}" _ipt_source="${_ipt_source}ip daddr @${_ipset}"
msg="${msg}NFTset${_ipset}】," msg="${msg}Nftset$(i18n "[%s]," "${_ipset}")"
unset _ipset unset _ipset
elif [ -n "$(echo ${i} | grep '^ip:')" ]; then elif [ -n "$(echo ${i} | grep '^ip:')" ]; then
_ip=$(echo ${i} | sed 's#ip:##g') _ip=$(echo ${i} | sed 's#ip:##g')
_ipt_source=$(factor ${_ip} "${_ipt_source}ip saddr") _ipt_source=$(factor ${_ip} "${_ipt_source}ip saddr")
msg="${msg}IP${_ip}】," msg="${msg}IP$(i18n "[%s]," "${_ip}")"
_ipv4="1" _ipv4="1"
unset _ip unset _ip
elif [ -n "$(echo ${i} | grep '^mac:')" ]; then elif [ -n "$(echo ${i} | grep '^mac:')" ]; then
_mac=$(echo ${i} | sed 's#mac:##g') _mac=$(echo ${i} | sed 's#mac:##g')
_ipt_source=$(factor ${_mac} "${_ipt_source}ether saddr") _ipt_source=$(factor ${_mac} "${_ipt_source}ether saddr")
msg="${msg}MAC${_mac}】," msg="${msg}MAC$(i18n "[%s]," "${_mac}")"
unset _mac unset _mac
elif [ -n "$(echo ${i} | grep '^any')" ]; then elif [ -n "$(echo ${i} | grep '^any')" ]; then
msg="${msg}所有设备," msg="${msg}$(i18n "All device,")"
else else
continue continue
fi fi
msg="$remarks】,${msg}" msg="$(i18n "[%s]," "${remarks}")${msg}"
[ "$tcp_no_redir_ports" != "disable" ] && { [ "$tcp_no_redir_ports" != "disable" ] && {
if ! has_1_65535 "$tcp_no_redir_ports"; then if ! has_1_65535 "$tcp_no_redir_ports"; then
nft "add rule $NFTABLE_NAME $nft_prerouting_chain ${_ipt_source} ip protocol tcp $(factor $tcp_no_redir_ports "tcp dport") counter return comment \"$remarks\"" nft "add rule $NFTABLE_NAME $nft_prerouting_chain ${_ipt_source} ip protocol tcp $(factor $tcp_no_redir_ports "tcp dport") counter return comment \"$remarks\""
[ "$_ipv4" != "1" ] && nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 ${_ipt_source} meta l4proto tcp $(factor $tcp_no_redir_ports "tcp dport") counter return comment \"$remarks\"" [ "$_ipv4" != "1" ] && nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 ${_ipt_source} meta l4proto tcp $(factor $tcp_no_redir_ports "tcp dport") counter return comment \"$remarks\""
echolog " - ${msg}不代理 TCP 端口[${tcp_no_redir_ports}]" log 2 "${msg}$(i18n "not proxy %s port [%s]" "TCP" "${tcp_no_redir_ports}")"
else else
#结束时会return无需加多余的规则。 # It will return when it ends, so no extra rules are needed.
tcp_proxy_mode="disable" tcp_proxy_mode="disable"
echolog " - ${msg}不代理所有 TCP" log 2 "${msg}$(i18n "not proxy all %s" "TCP")"
fi fi
} }
@@ -447,11 +443,11 @@ load_acl() {
if ! has_1_65535 "$udp_no_redir_ports"; then if ! has_1_65535 "$udp_no_redir_ports"; then
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp ${_ipt_source} $(factor $udp_no_redir_ports "udp dport") counter return comment \"$remarks\"" nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp ${_ipt_source} $(factor $udp_no_redir_ports "udp dport") counter return comment \"$remarks\""
[ "$_ipv4" != "1" ] && nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp ${_ipt_source} $(factor $udp_no_redir_ports "udp dport") counter return comment \"$remarks\"" 2>/dev/null [ "$_ipv4" != "1" ] && nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp ${_ipt_source} $(factor $udp_no_redir_ports "udp dport") counter return comment \"$remarks\"" 2>/dev/null
echolog " - ${msg}不代理 UDP 端口[${udp_no_redir_ports}]" log 2 "${msg}$(i18n "not proxy %s port [%s]" "UDP" "${udp_no_redir_ports}")"
else else
#结束时会return无需加多余的规则。 # It will return when it ends, so no extra rules are needed.
udp_proxy_mode="disable" udp_proxy_mode="disable"
echolog " - ${msg}不代理所有 UDP" log 2 "${msg}$(i18n "not proxy all %s" "UDP")"
fi fi
} }
@@ -461,7 +457,7 @@ load_acl() {
nft "add rule $NFTABLE_NAME PSW2_DNS ip protocol tcp ${_ipt_source} tcp dport 53 counter redirect to :$dns_redirect_port comment \"$remarks\"" nft "add rule $NFTABLE_NAME PSW2_DNS ip protocol tcp ${_ipt_source} tcp dport 53 counter redirect to :$dns_redirect_port comment \"$remarks\""
nft "add rule $NFTABLE_NAME PSW2_DNS meta l4proto udp ${_ipt_source} udp dport 53 counter redirect to :$dns_redirect_port comment \"$remarks\"" nft "add rule $NFTABLE_NAME PSW2_DNS meta l4proto udp ${_ipt_source} udp dport 53 counter redirect to :$dns_redirect_port comment \"$remarks\""
nft "add rule $NFTABLE_NAME PSW2_DNS meta l4proto tcp ${_ipt_source} tcp dport 53 counter redirect to :$dns_redirect_port comment \"$remarks\"" nft "add rule $NFTABLE_NAME PSW2_DNS meta l4proto tcp ${_ipt_source} tcp dport 53 counter redirect to :$dns_redirect_port comment \"$remarks\""
[ -z "$(get_cache_var "ACL_${sid}_default")" ] && echolog " - ${msg}使用与全局配置不相同节点已将DNS强制重定向到专用 DNS 服务器。" [ -z "$(get_cache_var "ACL_${sid}_default")" ] && log 2 "${msg}$(i18n "Using a node that is different from the global configuration, DNS has been forcibly redirected to a dedicated DNS server.")"
} }
else else
nft "add rule $NFTABLE_NAME PSW2_DNS ip protocol udp ${_ipt_source} udp dport 53 counter return comment \"$remarks\"" nft "add rule $NFTABLE_NAME PSW2_DNS ip protocol udp ${_ipt_source} udp dport 53 counter return comment \"$remarks\""
@@ -471,7 +467,7 @@ load_acl() {
fi fi
[ "$tcp_proxy_mode" != "disable" ] && [ -n "$redir_port" ] && { [ "$tcp_proxy_mode" != "disable" ] && [ -n "$redir_port" ] && {
msg2="${msg}使用 TCP 节点[$node_remark]" msg2="${msg}$(i18n "Use the %s node [%s]" "TCP" "${node_remark}")"
if [ -n "${is_tproxy}" ]; then if [ -n "${is_tproxy}" ]; then
msg2="${msg2}(TPROXY:${redir_port})" msg2="${msg2}(TPROXY:${redir_port})"
nft_chain="PSW2_MANGLE" nft_chain="PSW2_MANGLE"
@@ -507,13 +503,13 @@ load_acl() {
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp ${_ipt_source} $(factor $tcp_redir_ports "tcp dport") counter jump PSW2_RULE comment \"$remarks\"" 2>/dev/null nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp ${_ipt_source} $(factor $tcp_redir_ports "tcp dport") counter jump PSW2_RULE comment \"$remarks\"" 2>/dev/null
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp ${_ipt_source} $(REDIRECT $redir_port TPROXY) comment \"$remarks\"" 2>/dev/null nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp ${_ipt_source} $(REDIRECT $redir_port TPROXY) comment \"$remarks\"" 2>/dev/null
} }
echolog " - ${msg2}" log 2 "${msg2}"
} }
nft "add rule $NFTABLE_NAME $nft_prerouting_chain ip protocol tcp ${_ipt_source} counter return comment \"$remarks\"" nft "add rule $NFTABLE_NAME $nft_prerouting_chain ip protocol tcp ${_ipt_source} counter return comment \"$remarks\""
[ "$_ipv4" != "1" ] && nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp ${_ipt_source} counter return comment \"$remarks\"" 2>/dev/null [ "$_ipv4" != "1" ] && nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp ${_ipt_source} counter return comment \"$remarks\"" 2>/dev/null
[ "$udp_proxy_mode" != "disable" ] && [ -n "$redir_port" ] && { [ "$udp_proxy_mode" != "disable" ] && [ -n "$redir_port" ] && {
msg2="${msg}使用 UDP 节点[$node_remark](TPROXY:${redir_port})" msg2="${msg}$(i18n "Use the %s node [%s]" "UDP" "${node_remark}")(TPROXY:${redir_port})"
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp ${_ipt_source} ip daddr $FAKE_IP counter jump PSW2_RULE comment \"$remarks\"" nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp ${_ipt_source} ip daddr $FAKE_IP counter jump PSW2_RULE comment \"$remarks\""
add_shunt_t_rule "${shunt_list4}" "nft add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp ${_ipt_source} $(factor $udp_redir_ports "udp dport") ip daddr" "counter jump PSW2_RULE" "$remarks" add_shunt_t_rule "${shunt_list4}" "nft add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp ${_ipt_source} $(factor $udp_redir_ports "udp dport") ip daddr" "counter jump PSW2_RULE" "$remarks"
@@ -526,7 +522,7 @@ load_acl() {
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp ${_ipt_source} $(factor $udp_redir_ports "udp dport") counter jump PSW2_RULE comment \"$remarks\"" 2>/dev/null nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp ${_ipt_source} $(factor $udp_redir_ports "udp dport") counter jump PSW2_RULE comment \"$remarks\"" 2>/dev/null
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp ${_ipt_source} $(REDIRECT $redir_port TPROXY) comment \"$remarks\"" 2>/dev/null nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp ${_ipt_source} $(REDIRECT $redir_port TPROXY) comment \"$remarks\"" 2>/dev/null
} }
echolog " - ${msg2}" log 2 "${msg2}"
} }
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp ${_ipt_source} counter return comment \"$remarks\"" nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp ${_ipt_source} counter return comment \"$remarks\""
[ "$_ipv4" != "1" ] && nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp ${_ipt_source} counter return comment \"$remarks\"" 2>/dev/null [ "$_ipv4" != "1" ] && nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp ${_ipt_source} counter return comment \"$remarks\"" 2>/dev/null
@@ -538,42 +534,42 @@ load_acl() {
} }
[ "$ENABLED_DEFAULT_ACL" == 1 ] && [ "$CLIENT_PROXY" == 1 ] && { [ "$ENABLED_DEFAULT_ACL" == 1 ] && [ "$CLIENT_PROXY" == 1 ] && {
# 加载默认代理模式 local comment="$(i18n "Default")"
msg="【默认】," msg="$(i18n "[%s]," ${comment})"
[ "$TCP_NO_REDIR_PORTS" != "disable" ] && { [ "$TCP_NO_REDIR_PORTS" != "disable" ] && {
nft "add rule $NFTABLE_NAME $nft_prerouting_chain ip protocol tcp $(factor $TCP_NO_REDIR_PORTS "tcp dport") counter return comment \"默认\"" nft "add rule $NFTABLE_NAME $nft_prerouting_chain ip protocol tcp $(factor $TCP_NO_REDIR_PORTS "tcp dport") counter return comment \"${comment}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp $(factor $TCP_NO_REDIR_PORTS "tcp dport") counter return comment \"默认\"" nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp $(factor $TCP_NO_REDIR_PORTS "tcp dport") counter return comment \"${comment}\""
if ! has_1_65535 "$TCP_NO_REDIR_PORTS"; then if ! has_1_65535 "$TCP_NO_REDIR_PORTS"; then
echolog " - ${msg}不代理 TCP 端口[${TCP_NO_REDIR_PORTS}]" log 2 "${msg}$(i18n "not proxy %s port [%s]" "TCP" "${TCP_NO_REDIR_PORTS}")"
else else
TCP_PROXY_MODE="disable" TCP_PROXY_MODE="disable"
echolog " - ${msg}不代理所有 TCP 端口" log 2 "${msg}$(i18n "not proxy all %s" "TCP")"
fi fi
} }
[ "$UDP_NO_REDIR_PORTS" != "disable" ] && { [ "$UDP_NO_REDIR_PORTS" != "disable" ] && {
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp $(factor $UDP_NO_REDIR_PORTS "udp dport") counter return comment \"默认\"" nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp $(factor $UDP_NO_REDIR_PORTS "udp dport") counter return comment \"${comment}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 counter meta l4proto udp $(factor $UDP_NO_REDIR_PORTS "udp dport") counter return comment \"默认\"" nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 counter meta l4proto udp $(factor $UDP_NO_REDIR_PORTS "udp dport") counter return comment \"${comment}\""
if ! has_1_65535 "$UDP_NO_REDIR_PORTS"; then if ! has_1_65535 "$UDP_NO_REDIR_PORTS"; then
echolog " - ${msg}不代理 UDP 端口[${UDP_NO_REDIR_PORTS}]" log 2 "${msg}$(i18n "not proxy %s port [%s]" "UDP" "${UDP_NO_REDIR_PORTS}")"
else else
UDP_PROXY_MODE="disable" UDP_PROXY_MODE="disable"
echolog " - ${msg}不代理所有 UDP 端口" log 2 "${msg}$(i18n "not proxy all %s" "UDP")"
fi fi
} }
if ([ "$TCP_PROXY_MODE" != "disable" ] || [ "$UDP_PROXY_MODE" != "disable" ]) && [ -n "$NODE" ]; then if ([ "$TCP_PROXY_MODE" != "disable" ] || [ "$UDP_PROXY_MODE" != "disable" ]) && [ -n "$NODE" ]; then
[ -n "$DNS_REDIRECT_PORT" ] && { [ -n "$DNS_REDIRECT_PORT" ] && {
nft "add rule $NFTABLE_NAME PSW2_DNS ip protocol udp udp dport 53 counter redirect to :$DNS_REDIRECT_PORT comment \"默认\"" nft "add rule $NFTABLE_NAME PSW2_DNS ip protocol udp udp dport 53 counter redirect to :$DNS_REDIRECT_PORT comment \"${comment}\""
nft "add rule $NFTABLE_NAME PSW2_DNS ip protocol tcp tcp dport 53 counter redirect to :$DNS_REDIRECT_PORT comment \"默认\"" nft "add rule $NFTABLE_NAME PSW2_DNS ip protocol tcp tcp dport 53 counter redirect to :$DNS_REDIRECT_PORT comment \"${comment}\""
nft "add rule $NFTABLE_NAME PSW2_DNS meta l4proto udp udp dport 53 counter redirect to :$DNS_REDIRECT_PORT comment \"默认\"" nft "add rule $NFTABLE_NAME PSW2_DNS meta l4proto udp udp dport 53 counter redirect to :$DNS_REDIRECT_PORT comment \"${comment}\""
nft "add rule $NFTABLE_NAME PSW2_DNS meta l4proto tcp tcp dport 53 counter redirect to :$DNS_REDIRECT_PORT comment \"默认\"" nft "add rule $NFTABLE_NAME PSW2_DNS meta l4proto tcp tcp dport 53 counter redirect to :$DNS_REDIRECT_PORT comment \"${comment}\""
} }
fi fi
if [ "$TCP_PROXY_MODE" != "disable" ] && [ -n "$NODE" ]; then if [ "$TCP_PROXY_MODE" != "disable" ] && [ -n "$NODE" ]; then
msg2="${msg}使用 TCP 节点[$(config_n_get $NODE remarks)]" msg2="${msg}$(i18n "Use the %s node [%s]" "TCP" "$(config_n_get $NODE remarks)")"
if [ -n "${is_tproxy}" ]; then if [ -n "${is_tproxy}" ]; then
msg2="${msg2}(TPROXY:${REDIR_PORT})" msg2="${msg2}(TPROXY:${REDIR_PORT})"
nft_chain="PSW2_MANGLE" nft_chain="PSW2_MANGLE"
@@ -585,50 +581,50 @@ load_acl() {
fi fi
[ "$accept_icmp" = "1" ] && { [ "$accept_icmp" = "1" ] && {
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip protocol icmp ip daddr $FAKE_IP $(REDIRECT) comment \"默认\"" nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip protocol icmp ip daddr $FAKE_IP $(REDIRECT) comment \"${comment}\""
add_shunt_t_rule "${SHUNT_LIST4}" "nft add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip protocol icmp ip daddr" "$(REDIRECT)" "默认" add_shunt_t_rule "${SHUNT_LIST4}" "nft add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip protocol icmp ip daddr" "$(REDIRECT)" "${comment}"
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip protocol icmp $(REDIRECT) comment \"默认\"" nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip protocol icmp $(REDIRECT) comment \"${comment}\""
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip protocol icmp return comment \"默认\"" nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip protocol icmp return comment \"${comment}\""
} }
[ "$accept_icmpv6" = "1" ] && [ "$PROXY_IPV6" == "1" ] && { [ "$accept_icmpv6" = "1" ] && [ "$PROXY_IPV6" == "1" ] && {
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 ip6 daddr $FAKE_IP_6 $(REDIRECT) comment \"默认\"" nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 ip6 daddr $FAKE_IP_6 $(REDIRECT) comment \"${comment}\""
add_shunt_t_rule "${SHUNT_LIST6}" "nft add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 ip6 daddr" "$(REDIRECT)" "默认" add_shunt_t_rule "${SHUNT_LIST6}" "nft add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 ip6 daddr" "$(REDIRECT)" "${comment}"
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 $(REDIRECT) comment \"默认\"" nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 $(REDIRECT) comment \"${comment}\""
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 return comment \"默认\"" nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 return comment \"${comment}\""
} }
nft "add rule $NFTABLE_NAME $nft_chain ip protocol tcp ip daddr $FAKE_IP ${nft_j} comment \"默认\"" nft "add rule $NFTABLE_NAME $nft_chain ip protocol tcp ip daddr $FAKE_IP ${nft_j} comment \"${comment}\""
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}" "默认" 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}" "${comment}"
nft "add rule $NFTABLE_NAME $nft_chain ip protocol tcp $(factor $TCP_REDIR_PORTS "tcp dport") ${nft_j} comment \"默认\"" nft "add rule $NFTABLE_NAME $nft_chain ip protocol tcp $(factor $TCP_REDIR_PORTS "tcp dport") ${nft_j} comment \"${comment}\""
[ -n "${is_tproxy}" ] && nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol tcp $(REDIRECT $REDIR_PORT TPROXY4) comment \"默认\"" [ -n "${is_tproxy}" ] && nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol tcp $(REDIRECT $REDIR_PORT TPROXY4) comment \"${comment}\""
[ "$PROXY_IPV6" == "1" ] && { [ "$PROXY_IPV6" == "1" ] && {
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp ip6 daddr $FAKE_IP_6 jump PSW2_RULE comment \"默认\"" nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp ip6 daddr $FAKE_IP_6 jump PSW2_RULE comment \"${comment}\""
add_shunt_t_rule "${SHUNT_LIST6}" "nft add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp $(factor $TCP_REDIR_PORTS "tcp dport") ip6 daddr" "${nft_j}" "默认" add_shunt_t_rule "${SHUNT_LIST6}" "nft add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp $(factor $TCP_REDIR_PORTS "tcp dport") ip6 daddr" "${nft_j}" "${comment}"
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp $(factor $TCP_REDIR_PORTS "tcp dport") counter jump PSW2_RULE comment \"默认\"" nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp $(factor $TCP_REDIR_PORTS "tcp dport") counter jump PSW2_RULE comment \"${comment}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp $(REDIRECT $REDIR_PORT TPROXY) comment \"默认\"" nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp $(REDIRECT $REDIR_PORT TPROXY) comment \"${comment}\""
} }
echolog "${msg2}" log 2 "${msg2}"
fi fi
if [ "$UDP_PROXY_MODE" != "disable" ] && [ -n "$NODE" ]; then if [ "$UDP_PROXY_MODE" != "disable" ] && [ -n "$NODE" ]; then
msg2="${msg}使用 UDP 节点[$(config_n_get $NODE remarks)](TPROXY:${REDIR_PORT})" msg2="${msg}$(i18n "Use the %s node [%s]" "UDP" "$(config_n_get $NODE remarks)")(TPROXY:${REDIR_PORT})"
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp ip daddr $FAKE_IP counter jump PSW2_RULE comment \"默认\"" nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp ip daddr $FAKE_IP counter jump PSW2_RULE comment \"${comment}\""
add_shunt_t_rule "${SHUNT_LIST4}" "nft add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp $(factor $UDP_REDIR_PORTS "udp dport") ip daddr" "counter jump PSW2_RULE" "默认" add_shunt_t_rule "${SHUNT_LIST4}" "nft add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp $(factor $UDP_REDIR_PORTS "udp dport") ip daddr" "counter jump PSW2_RULE" "${comment}"
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp $(factor $UDP_REDIR_PORTS "udp dport") counter jump PSW2_RULE comment \"默认\"" nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp $(factor $UDP_REDIR_PORTS "udp dport") counter jump PSW2_RULE comment \"${comment}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp $(REDIRECT $REDIR_PORT TPROXY4) comment \"默认\"" nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp $(REDIRECT $REDIR_PORT TPROXY4) comment \"${comment}\""
[ "$PROXY_IPV6" == "1" ] && { [ "$PROXY_IPV6" == "1" ] && {
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp ip6 daddr $FAKE_IP_6 jump PSW2_RULE comment \"默认\"" nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp ip6 daddr $FAKE_IP_6 jump PSW2_RULE comment \"${comment}\""
add_shunt_t_rule "${SHUNT_LIST6}" "nft add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp $(factor $UDP_REDIR_PORTS "udp dport") ip6 daddr" "counter jump PSW2_RULE" "默认" add_shunt_t_rule "${SHUNT_LIST6}" "nft add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp $(factor $UDP_REDIR_PORTS "udp dport") ip6 daddr" "counter jump PSW2_RULE" "${comment}"
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp $(factor $UDP_REDIR_PORTS "udp dport") counter jump PSW2_RULE comment \"默认\"" nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp $(factor $UDP_REDIR_PORTS "udp dport") counter jump PSW2_RULE comment \"${comment}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp $(REDIRECT $REDIR_PORT TPROXY) comment \"默认\"" nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp $(REDIRECT $REDIR_PORT TPROXY) comment \"${comment}\""
} }
echolog "${msg2}" log 2 "${msg2}"
udp_flag=1 udp_flag=1
fi fi
} }
@@ -639,7 +635,7 @@ filter_haproxy() {
local ip=$(get_host_ip ipv4 $(echo $item | awk -F ":" '{print $1}') 1) local ip=$(get_host_ip ipv4 $(echo $item | awk -F ":" '{print $1}') 1)
[ -n "$ip" ] && insert_nftset $NFTSET_VPS "-1" $ip [ -n "$ip" ] && insert_nftset $NFTSET_VPS "-1" $ip
done done
echolog "加入负载均衡的节点到nftset[$NFTSET_VPS]直连完成" log_i18n 1 "Add node to the load balancer is directly connected to %s[%s]." "nftset" "${NFTSET_VPS}"
} }
filter_vps_addr() { filter_vps_addr() {
@@ -653,9 +649,9 @@ filter_vps_addr() {
filter_vpsip() { filter_vpsip() {
insert_nftset $NFTSET_VPS "-1" $(uci show $CONFIG | grep -E "(.address=|.download_address=)" | cut -d "'" -f 2 | grep -E "([0-9]{1,3}[\.]){3}[0-9]{1,3}" | grep -v "^127\.0\.0\.1$" | sed -e "/^$/d") insert_nftset $NFTSET_VPS "-1" $(uci show $CONFIG | grep -E "(.address=|.download_address=)" | cut -d "'" -f 2 | grep -E "([0-9]{1,3}[\.]){3}[0-9]{1,3}" | grep -v "^127\.0\.0\.1$" | sed -e "/^$/d")
echolog " - [$?]加入所有IPv4节点到nftset[$NFTSET_VPS]直连完成" #log 1 "$(i18n "Add all %s nodes to %s[%s] direct connection complete." "IPv4" "nftset" "${$NFTSET_VPS}")"
insert_nftset $NFTSET_VPS6 "-1" $(uci show $CONFIG | grep -E "(.address=|.download_address=)" | cut -d "'" -f 2 | grep -E "([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4}" | sed -e "/^$/d") insert_nftset $NFTSET_VPS6 "-1" $(uci show $CONFIG | grep -E "(.address=|.download_address=)" | cut -d "'" -f 2 | grep -E "([A-Fa-f0-9]{1,4}::?){1,7}[A-Fa-f0-9]{1,4}" | sed -e "/^$/d")
echolog " - [$?]加入所有IPv6节点到nftset[$NFTSET_VPS6]直连完成" #log 1 "$(i18n "Add all %s nodes to %s[%s] direct connection complete." "IPv6" "nftset" "${$NFTSET_VPS6}")"
} }
filter_server_port() { filter_server_port() {
@@ -701,7 +697,7 @@ filter_direct_node_list() {
} }
add_firewall_rule() { add_firewall_rule() {
echolog "开始加载 nftables 防火墙规则..." log_i18n 0 "Starting to load %s firewall rules..." "nftables"
gen_nft_tables gen_nft_tables
gen_nftset $NFTSET_LOCAL ipv4_addr 0 "-1" gen_nftset $NFTSET_LOCAL ipv4_addr 0 "-1"
gen_nftset $NFTSET_LAN ipv4_addr 0 "-1" $(gen_lanlist) gen_nftset $NFTSET_LAN ipv4_addr 0 "-1" $(gen_lanlist)
@@ -714,32 +710,30 @@ add_firewall_rule() {
insert_nftset $NFTSET_LOCAL "-1" $(ip address show | grep -w "inet" | awk '{print $2}' | awk -F '/' '{print $1}' | sed -e "s/ /\n/g") insert_nftset $NFTSET_LOCAL "-1" $(ip address show | grep -w "inet" | awk '{print $2}' | awk -F '/' '{print $1}' | sed -e "s/ /\n/g")
insert_nftset $NFTSET_LOCAL6 "-1" $(ip address show | grep -w "inet6" | awk '{print $2}' | awk -F '/' '{print $1}' | sed -e "s/ /\n/g") insert_nftset $NFTSET_LOCAL6 "-1" $(ip address show | grep -w "inet6" | awk '{print $2}' | awk -F '/' '{print $1}' | sed -e "s/ /\n/g")
# 忽略特殊IP段 # Ignore special IP ranges
local lan_ifname lan_ip local lan_ifname lan_ip
lan_ifname=$(uci -q -p /tmp/state get network.lan.ifname) lan_ifname=$(uci -q -p /tmp/state get network.lan.ifname)
[ -n "$lan_ifname" ] && { [ -n "$lan_ifname" ] && {
lan_ip=$(ip address show $lan_ifname | grep -w "inet" | awk '{print $2}') lan_ip=$(ip address show $lan_ifname | grep -w "inet" | awk '{print $2}')
lan_ip6=$(ip address show $lan_ifname | grep -w "inet6" | awk '{print $2}') lan_ip6=$(ip address show $lan_ifname | grep -w "inet6" | awk '{print $2}')
#echolog "本机IPv4网段互访直连${lan_ip}" #log_i18n 1 "local network segments (%s) direct connection: %s" "IPv4" "${lan_ip}"
#echolog "本机IPv6网段互访直连${lan_ip6}" #log_i18n 1 "local network segments (%s) direct connection: %s" "IPv6" "${lan_ip6}"
[ -n "$lan_ip" ] && insert_nftset $NFTSET_LAN "-1" $(echo $lan_ip | sed -e "s/ /\n/g") [ -n "$lan_ip" ] && insert_nftset $NFTSET_LAN "-1" $(echo $lan_ip | sed -e "s/ /\n/g")
[ -n "$lan_ip6" ] && insert_nftset $NFTSET_LAN6 "-1" $(echo $lan_ip6 | sed -e "s/ /\n/g") [ -n "$lan_ip6" ] && insert_nftset $NFTSET_LAN6 "-1" $(echo $lan_ip6 | sed -e "s/ /\n/g")
} }
[ -n "$ISP_DNS" ] && { [ -n "$ISP_DNS" ] && {
#echolog "处理 ISP DNS 例外..."
for ispip in $ISP_DNS; do for ispip in $ISP_DNS; do
insert_nftset $NFTSET_LAN "-1" $ispip insert_nftset $NFTSET_LAN "-1" $ispip
echolog " - [$?]追加ISP IPv4 DNS到白名单${ispip}" log_i18n 1 "$(i18n "Add ISP %s DNS to the whitelist: %s" "IPv4" "${ispip}")"
done done
} }
[ -n "$ISP_DNS6" ] && { [ -n "$ISP_DNS6" ] && {
#echolog "处理 ISP IPv6 DNS 例外..."
for ispip6 in $ISP_DNS6; do for ispip6 in $ISP_DNS6; do
insert_nftset $NFTSET_LAN6 "-1" $ispip6 insert_nftset $NFTSET_LAN6 "-1" $ispip6
echolog " - [$?]追加ISP IPv6 DNS到白名单${ispip6}" log_i18n 1 "$(i18n "Add ISP %s DNS to the whitelist: %s" "IPv6" "${ispip6}")"
done done
} }
@@ -748,10 +742,10 @@ add_firewall_rule() {
gen_nftset $nftset_global_white ipv4_addr 0 0 gen_nftset $nftset_global_white ipv4_addr 0 0
gen_nftset $nftset_global_white6 ipv6_addr 0 0 gen_nftset $nftset_global_white6 ipv6_addr 0 0
#分流规则的IP列表(使用分流节点时导入) # Shunt rules IP list (import when use shunt node)
gen_shunt_list "${NODE}" SHUNT_LIST4 SHUNT_LIST6 ${WRITE_IPSET_DIRECT} ${nftset_global_white} ${nftset_global_white6} gen_shunt_list "${NODE}" SHUNT_LIST4 SHUNT_LIST6 ${WRITE_IPSET_DIRECT} ${nftset_global_white} ${nftset_global_white6}
# 过滤所有节点IP # Filter all node IPs
filter_vpsip > /dev/null 2>&1 & filter_vpsip > /dev/null 2>&1 &
filter_haproxy > /dev/null 2>&1 & filter_haproxy > /dev/null 2>&1 &
# Prevent some conditions # Prevent some conditions
@@ -807,7 +801,7 @@ add_firewall_rule() {
local dns_address=$(echo $auto_dns | awk -F '#' '{print $1}') local dns_address=$(echo $auto_dns | awk -F '#' '{print $1}')
local dns_port=$(echo $auto_dns | awk -F '#' '{print $2}') local dns_port=$(echo $auto_dns | awk -F '#' '{print $2}')
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE ip protocol udp ip daddr ${dns_address} $(factor ${dns_port:-53} "udp dport") counter return" nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE ip protocol udp ip daddr ${dns_address} $(factor ${dns_port:-53} "udp dport") counter return"
echolog " - [$?]追加直连DNS到nftables${dns_address}:${dns_port:-53}" log_i18n 1 "$(i18n "Add direct DNS to %s: %s" "nftables" "${dns_address}:${dns_port:-53}")"
done done
} }
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE meta mark 0xff counter return" nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE meta mark 0xff counter return"
@@ -888,15 +882,15 @@ add_firewall_rule() {
TCP_LOCALHOST_PROXY=$LOCALHOST_PROXY TCP_LOCALHOST_PROXY=$LOCALHOST_PROXY
UDP_LOCALHOST_PROXY=$LOCALHOST_PROXY UDP_LOCALHOST_PROXY=$LOCALHOST_PROXY
msg="【路由器本机】," msg="$(i18n "[Local],")"
[ "$TCP_NO_REDIR_PORTS" != "disable" ] && { [ "$TCP_NO_REDIR_PORTS" != "disable" ] && {
nft "add rule $NFTABLE_NAME $nft_output_chain ip protocol tcp $(factor $TCP_NO_REDIR_PORTS "tcp dport") counter return" nft "add rule $NFTABLE_NAME $nft_output_chain ip protocol tcp $(factor $TCP_NO_REDIR_PORTS "tcp dport") counter return"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto tcp $(factor $TCP_NO_REDIR_PORTS "tcp dport") counter return" nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto tcp $(factor $TCP_NO_REDIR_PORTS "tcp dport") counter return"
if ! has_1_65535 "$TCP_NO_REDIR_PORTS"; then if ! has_1_65535 "$TCP_NO_REDIR_PORTS"; then
echolog " - ${msg}不代理 TCP 端口[${TCP_NO_REDIR_PORTS}]" log 1 "${msg}$(i18n "not proxy %s port [%s]" "TCP" "${TCP_NO_REDIR_PORTS}")"
else else
unset TCP_LOCALHOST_PROXY unset TCP_LOCALHOST_PROXY
echolog " - ${msg}不代理所有 TCP" log 1 "${msg}$(i18n "not proxy all %s" "TCP")"
fi fi
} }
@@ -904,10 +898,10 @@ add_firewall_rule() {
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE ip protocol udp $(factor $UDP_NO_REDIR_PORTS "udp dport") counter return" nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE ip protocol udp $(factor $UDP_NO_REDIR_PORTS "udp dport") counter return"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto udp $(factor $UDP_NO_REDIR_PORTS "udp dport") counter return" nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto udp $(factor $UDP_NO_REDIR_PORTS "udp dport") counter return"
if ! has_1_65535 "$UDP_NO_REDIR_PORTS"; then if ! has_1_65535 "$UDP_NO_REDIR_PORTS"; then
echolog " - ${msg}不代理 UDP 端口[${UDP_NO_REDIR_PORTS}]" log 1 "${msg}$(i18n "not proxy %s port [%s]" "UDP" "${UDP_NO_REDIR_PORTS}")"
else else
unset UDP_LOCALHOST_PROXY unset UDP_LOCALHOST_PROXY
echolog " - ${msg}不代理所有 UDP" log 1 "${msg}$(i18n "not proxy all %s" "UDP")"
fi fi
} }
@@ -920,7 +914,9 @@ add_firewall_rule() {
} }
fi fi
# 加载路由器自身代理 TCP local comment_l="$(i18n "Local")"
# Loading local router proxy TCP
if [ -n "$NODE" ] && [ "$TCP_LOCALHOST_PROXY" = "1" ]; then if [ -n "$NODE" ] && [ "$TCP_LOCALHOST_PROXY" = "1" ]; then
[ "$accept_icmp" = "1" ] && { [ "$accept_icmp" = "1" ] && {
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT oif lo ip protocol icmp ip daddr $FAKE_IP counter redirect" nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT oif lo ip protocol icmp ip daddr $FAKE_IP counter redirect"
@@ -949,8 +945,8 @@ add_firewall_rule() {
nft "add rule $NFTABLE_NAME $nft_chain ip protocol tcp $(factor $TCP_REDIR_PORTS "tcp dport") ${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 protocol tcp counter jump PSW2_OUTPUT_NAT"
[ -n "${is_tproxy}" ] && { [ -n "${is_tproxy}" ] && {
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol tcp iif lo $(REDIRECT $REDIR_PORT TPROXY4) comment \"本机\"" 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 \"本机\"" 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 protocol tcp counter jump PSW2_OUTPUT_MANGLE comment \"PSW2_OUTPUT_MANGLE\""
} }
@@ -958,8 +954,8 @@ add_firewall_rule() {
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto tcp ip6 daddr $FAKE_IP_6 jump PSW2_RULE" nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto tcp ip6 daddr $FAKE_IP_6 jump PSW2_RULE"
add_shunt_t_rule "${SHUNT_LIST6}" "nft add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto tcp $(factor $TCP_REDIR_PORTS "tcp dport") ip6 daddr" "counter jump PSW2_RULE" add_shunt_t_rule "${SHUNT_LIST6}" "nft add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto tcp $(factor $TCP_REDIR_PORTS "tcp dport") ip6 daddr" "counter jump PSW2_RULE"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto tcp $(factor $TCP_REDIR_PORTS "tcp dport") counter jump PSW2_RULE" nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto tcp $(factor $TCP_REDIR_PORTS "tcp dport") counter jump PSW2_RULE"
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp iif lo $(REDIRECT $REDIR_PORT TPROXY) comment \"本机\"" nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp iif lo $(REDIRECT $REDIR_PORT TPROXY) comment \"${comment_l}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp iif lo counter return comment \"本机\"" nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp iif lo counter return comment \"${comment_l}\""
} }
[ -d "${TMP_IFACE_PATH}" ] && { [ -d "${TMP_IFACE_PATH}" ] && {
@@ -970,21 +966,21 @@ add_firewall_rule() {
} }
fi fi
# 加载路由器自身代理 UDP # Loading local router proxy UDP
if [ -n "$NODE" ] && [ "$UDP_LOCALHOST_PROXY" = "1" ]; then if [ -n "$NODE" ] && [ "$UDP_LOCALHOST_PROXY" = "1" ]; then
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE ip protocol udp ip daddr $FAKE_IP counter jump PSW2_RULE" nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE ip protocol udp ip daddr $FAKE_IP counter jump PSW2_RULE"
add_shunt_t_rule "${SHUNT_LIST4}" "nft add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE ip protocol udp $(factor $UDP_REDIR_PORTS "udp dport") ip daddr" "counter jump PSW2_RULE" add_shunt_t_rule "${SHUNT_LIST4}" "nft add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE ip protocol udp $(factor $UDP_REDIR_PORTS "udp dport") ip daddr" "counter jump PSW2_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_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 \"本机\"" 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 \"本机\"" 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 protocol udp counter jump PSW2_OUTPUT_MANGLE comment \"PSW2_OUTPUT_MANGLE\""
[ "$PROXY_IPV6" == "1" ] && { [ "$PROXY_IPV6" == "1" ] && {
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto udp ip6 daddr $FAKE_IP_6 jump PSW2_RULE" nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto udp ip6 daddr $FAKE_IP_6 jump PSW2_RULE"
add_shunt_t_rule "${SHUNT_LIST6}" "nft add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto udp $(factor $UDP_REDIR_PORTS "udp dport") ip6 daddr" "counter jump PSW2_RULE" add_shunt_t_rule "${SHUNT_LIST6}" "nft add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto udp $(factor $UDP_REDIR_PORTS "udp dport") ip6 daddr" "counter jump PSW2_RULE"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto udp $(factor $UDP_REDIR_PORTS "udp dport") counter jump PSW2_RULE" nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto udp $(factor $UDP_REDIR_PORTS "udp dport") counter jump PSW2_RULE"
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp iif lo $(REDIRECT $REDIR_PORT TPROXY) comment \"本机\"" nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp iif lo $(REDIRECT $REDIR_PORT TPROXY) comment \"${comment_l}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp iif lo counter return comment \"本机\"" nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp iif lo counter return comment \"${comment_l}\""
} }
[ -d "${TMP_IFACE_PATH}" ] && { [ -d "${TMP_IFACE_PATH}" ] && {
@@ -1002,12 +998,11 @@ add_firewall_rule() {
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp udp dport 53 counter return" nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp udp dport 53 counter return"
} }
# 加载ACLS
load_acl load_acl
filter_direct_node_list > /dev/null 2>&1 & filter_direct_node_list > /dev/null 2>&1 &
echolog "防火墙规则加载完成!" log_i18n 0 "%s firewall rules load complete!" "nftables"
} }
del_firewall_rule() { del_firewall_rule() {
@@ -1039,11 +1034,11 @@ del_firewall_rule() {
destroy_nftset $NFTSET_LAN6 destroy_nftset $NFTSET_LAN6
destroy_nftset $NFTSET_VPS6 destroy_nftset $NFTSET_VPS6
$DIR/app.sh echolog "删除 nftables 规则完成。" $DIR/app.sh log_i18n 0 "Delete %s rules is complete." "nftables"
} }
flush_nftset() { flush_nftset() {
$DIR/app.sh echolog "清空 NFTSet" $DIR/app.sh log_i18n 0 "Clear %s." "NFTSet"
for _name in $(nft -a list sets | grep -E "passwall2" | awk -F 'set ' '{print $2}' | awk '{print $1}'); do for _name in $(nft -a list sets | grep -E "passwall2" | awk -F 'set ' '{print $2}' | awk '{print $1}'); do
destroy_nftset ${_name} destroy_nftset ${_name}
done done

View File

@@ -49,9 +49,7 @@ local function curl(url, file)
return tonumber(result) return tonumber(result)
end end
--获取geoip
local function fetch_geoip() local function fetch_geoip()
--请求geoip
xpcall(function() xpcall(function()
local return_code, content = api.curl_logic(geoip_api) local return_code, content = api.curl_logic(geoip_api)
local json = jsonc.parse(content) local json = jsonc.parse(content)
@@ -70,7 +68,7 @@ local function fetch_geoip()
if fs.access(asset_location .. "geoip.dat") then if fs.access(asset_location .. "geoip.dat") then
sys.call(string.format("cp -f %s %s", asset_location .. "geoip.dat", "/tmp/geoip.dat")) sys.call(string.format("cp -f %s %s", asset_location .. "geoip.dat", "/tmp/geoip.dat"))
if sys.call('sha256sum -c /tmp/geoip.dat.sha256sum > /dev/null 2>&1') == 0 then if sys.call('sha256sum -c /tmp/geoip.dat.sha256sum > /dev/null 2>&1') == 0 then
log("geoip 版本一致,无需更新。") log(api.i18n.translatef("%s version is the same and does not need to be updated.", "geoip"))
return 1 return 1
end end
end end
@@ -80,10 +78,10 @@ local function fetch_geoip()
if sys.call('sha256sum -c /tmp/geoip.dat.sha256sum > /dev/null 2>&1') == 0 then if sys.call('sha256sum -c /tmp/geoip.dat.sha256sum > /dev/null 2>&1') == 0 then
sys.call(string.format("mkdir -p %s && cp -f %s %s", asset_location, "/tmp/geoip.dat", asset_location .. "geoip.dat")) sys.call(string.format("mkdir -p %s && cp -f %s %s", asset_location, "/tmp/geoip.dat", asset_location .. "geoip.dat"))
reboot = 1 reboot = 1
log("geoip 更新成功。") log(api.i18n.translatef("%s update success.", "geoip"))
return 1 return 1
else else
log("geoip 更新失败,请稍后再试。") log(api.i18n.translatef("%s update failed, please try again later.", "geoip"))
end end
break break
end end
@@ -103,9 +101,7 @@ local function fetch_geoip()
return 0 return 0
end end
--获取geosite
local function fetch_geosite() local function fetch_geosite()
--请求geosite
xpcall(function() xpcall(function()
local return_code, content = api.curl_logic(geosite_api) local return_code, content = api.curl_logic(geosite_api)
local json = jsonc.parse(content) local json = jsonc.parse(content)
@@ -124,7 +120,7 @@ local function fetch_geosite()
if fs.access(asset_location .. "geosite.dat") then if fs.access(asset_location .. "geosite.dat") then
sys.call(string.format("cp -f %s %s", asset_location .. "geosite.dat", "/tmp/geosite.dat")) sys.call(string.format("cp -f %s %s", asset_location .. "geosite.dat", "/tmp/geosite.dat"))
if sys.call('sha256sum -c /tmp/geosite.dat.sha256sum > /dev/null 2>&1') == 0 then if sys.call('sha256sum -c /tmp/geosite.dat.sha256sum > /dev/null 2>&1') == 0 then
log("geosite 版本一致,无需更新。") log(api.i18n.translatef("%s version is the same and does not need to be updated.", "geosite"))
return 1 return 1
end end
end end
@@ -134,10 +130,10 @@ local function fetch_geosite()
if sys.call('sha256sum -c /tmp/geosite.dat.sha256sum > /dev/null 2>&1') == 0 then if sys.call('sha256sum -c /tmp/geosite.dat.sha256sum > /dev/null 2>&1') == 0 then
sys.call(string.format("mkdir -p %s && cp -f %s %s", asset_location, "/tmp/geosite.dat", asset_location .. "geosite.dat")) sys.call(string.format("mkdir -p %s && cp -f %s %s", asset_location, "/tmp/geosite.dat", asset_location .. "geosite.dat"))
reboot = 1 reboot = 1
log("geosite 更新成功。") log(api.i18n.translatef("%s update success.", "geosite"))
return 1 return 1
else else
log("geosite 更新失败,请稍后再试。") log(api.i18n.translatef("%s update failed, please try again later.", "geosite"))
end end
break break
end end
@@ -174,17 +170,17 @@ if geoip_update == 0 and geosite_update == 0 then
os.exit(0) os.exit(0)
end end
log("开始更新规则...") log(api.i18n.translate("Start updating the rules..."))
if tonumber(geoip_update) == 1 then if tonumber(geoip_update) == 1 then
log("geoip 开始更新...") log(api.i18n.translatef("%s Start updating...", "geoip"))
local status = fetch_geoip() local status = fetch_geoip()
os.remove("/tmp/geoip.dat") os.remove("/tmp/geoip.dat")
os.remove("/tmp/geoip.dat.sha256sum") os.remove("/tmp/geoip.dat.sha256sum")
end end
if tonumber(geosite_update) == 1 then if tonumber(geosite_update) == 1 then
log("geosite 开始更新...") log(api.i18n.translatef("%s Start updating...", "geosite"))
local status = fetch_geosite() local status = fetch_geosite()
os.remove("/tmp/geosite.dat") os.remove("/tmp/geosite.dat")
os.remove("/tmp/geosite.dat.sha256sum") os.remove("/tmp/geosite.dat.sha256sum")
@@ -201,8 +197,8 @@ if reboot == 1 then
end end
end end
log("重启服务,应用新的规则。") log(api.i18n.translate("Restart the service and apply the new rules."))
uci:set(name, "@global[0]", "flush_set", "1") uci:set(name, "@global[0]", "flush_set", "1")
api.uci_save(uci, name, true, true) api.uci_save(uci, name, true, true)
end end
log("规则更新完毕...") log(api.i18n.translate("The rules have been updated..."))

View File

@@ -1,17 +1,11 @@
#!/bin/sh #!/bin/sh
CONFIG=passwall2 CONFIG=passwall2
LOG_FILE=/tmp/log/$CONFIG.log APP_FILE=/usr/share/${CONFIG}/app.sh
LOCK_FILE_DIR=/tmp/lock LOCK_FILE_DIR=/tmp/lock
flag=0 flag=0
echolog() {
local d="$(date "+%Y-%m-%d %H:%M:%S")"
#echo -e "$d: $1"
echo -e "$d: $1" >> $LOG_FILE
}
config_n_get() { config_n_get() {
local ret=$(uci -q get "${CONFIG}.${1}.${2}" 2>/dev/null) local ret=$(uci -q get "${CONFIG}.${1}.${2}" 2>/dev/null)
echo "${ret:=$3}" echo "${ret:=$3}"
@@ -60,12 +54,12 @@ test_node() {
local node_id=$1 local node_id=$1
local _type=$(echo $(config_n_get ${node_id} type) | tr 'A-Z' 'a-z') local _type=$(echo $(config_n_get ${node_id} type) | tr 'A-Z' 'a-z')
[ -n "${_type}" ] && { [ -n "${_type}" ] && {
local _tmp_port=$(/usr/share/${CONFIG}/app.sh get_new_port 61080 tcp,udp) local _tmp_port=$($APP_FILE get_new_port 61080 tcp,udp)
/usr/share/${CONFIG}/app.sh run_socks flag="test_node_${node_id}" node=${node_id} bind=127.0.0.1 socks_port=${_tmp_port} config_file=test_node_${node_id}.json $APP_FILE 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}" local curlx="socks5h://127.0.0.1:${_tmp_port}"
sleep 1s sleep 1s
local _proxy_status=$(test_url "${probe_url}" ${retry_num} ${connect_timeout} "-x $curlx") local _proxy_status=$(test_url "${probe_url}" ${retry_num} ${connect_timeout} "-x $curlx")
# 结束 SS 插件进程 # Kill the SS plugin process
local pid_file="/tmp/etc/${CONFIG}/test_node_${node_id}_plugin.pid" local pid_file="/tmp/etc/${CONFIG}/test_node_${node_id}_plugin.pid"
[ -s "$pid_file" ] && kill -9 "$(head -n 1 "$pid_file")" >/dev/null 2>&1 [ -s "$pid_file" ] && kill -9 "$(head -n 1 "$pid_file")" >/dev/null 2>&1
pgrep -af "test_node_${node_id}" | awk '! /socks_auto_switch\.sh/{print $1}' | xargs kill -9 >/dev/null 2>&1 pgrep -af "test_node_${node_id}" | awk '! /socks_auto_switch\.sh/{print $1}' | xargs kill -9 >/dev/null 2>&1
@@ -82,10 +76,10 @@ test_auto_switch() {
local b_nodes=$1 local b_nodes=$1
local now_node=$2 local now_node=$2
[ -z "$now_node" ] && { [ -z "$now_node" ] && {
if [ -n "$(/usr/share/${CONFIG}/app.sh get_cache_var "socks_${id}")" ]; then if [ -n "$($APP_FILE get_cache_var "socks_${id}")" ]; then
now_node=$(/usr/share/${CONFIG}/app.sh get_cache_var "socks_${id}") now_node=$($APP_FILE get_cache_var "socks_${id}")
else else
#echolog "Socks切换检测未知错误" #$APP_FILE log_i18n 0 "Socks switch detection: Unknown error."
return 1 return 1
fi fi
} }
@@ -96,46 +90,50 @@ test_auto_switch() {
local status=$(test_proxy) local status=$(test_proxy)
if [ "$status" = "2" ]; then if [ "$status" = "2" ]; then
echolog "Socks切换检测无法连接到网络请检查网络是否正常" $APP_FILE log_i18n 0 "Socks switch detection: Unable to connect to the network. Please check if the network is working properly!"
return 2 return 2
fi fi
#检测主节点是否能使用 # Check if the main node is usable
if [ "$restore_switch" = "1" ] && [ -n "$main_node" ] && [ "$now_node" != "$main_node" ]; then if [ "$restore_switch" = "1" ] && [ -n "$main_node" ] && [ "$now_node" != "$main_node" ]; then
test_node ${main_node} test_node ${main_node}
[ $? -eq 0 ] && { [ $? -eq 0 ] && {
#主节点正常,切换到主节点 # The main node is working properly; switch to the main node.
echolog "Socks切换检测:${id}主节点【$(config_n_get $main_node type)[$(config_n_get $main_node remarks)]】正常,切换到主节点!" $APP_FILE log_i18n 0 "Socks switch detection: Primary node 【%s: [%s]】 is normal. Switch to the primary node!" "${id}" "$(config_n_get $main_node type)" "$(config_n_get $main_node remarks)"
/usr/share/${CONFIG}/app.sh socks_node_switch flag=${id} new_node=${main_node} $APP_FILE socks_node_switch flag=${id} new_node=${main_node}
[ $? -eq 0 ] && { [ $? -eq 0 ] && {
echolog "Socks切换检测${id}节点切换完毕!" $APP_FILE log_i18n 0 "Socks switch detection: %s node switch complete!" "${id}"
} }
return 0 return 0
} }
fi fi
if [ "$status" = "0" ]; then if [ "$status" = "0" ]; then
#echolog "Socks切换检测${id}$(config_n_get $now_node type)[$(config_n_get $now_node remarks)]】正常。" #$APP_FILE log_i18n 0 "Socks switch detection: %s 【%s:[%s]】 normal." "${id}" "$(config_n_get $now_node type)" "$(config_n_get $now_node remarks)"
return 0 return 0
elif [ "$status" = "1" ]; then elif [ "$status" = "1" ]; then
local new_node msg local new_node msg
if [ "$backup_node_num" -gt 1 ]; then if [ "$backup_node_num" -gt 1 ]; then
# 有多个后备节点时 # When there are multiple backup nodes
local first_node found node local first_node found node
for node in $b_nodes; do for node in $b_nodes; do
[ -z "$first_node" ] && first_node="$node" # 记录第一个节点 [ -z "$first_node" ] && first_node="$node" # Record the first node.
[ "$found" = "1" ] && { new_node="$node"; break; } # 找到当前节点后取下一个 [ "$found" = "1" ] && { new_node="$node"; break; } # Find the current node and then retrieve the next one.
[ "$node" = "$now_node" ] && found=1 # 标记找到当前节点 [ "$node" = "$now_node" ] && found=1 # Mark the current node found.
done done
# 如果没找到当前节点,或者当前节点是最后一个,就取第一个节点 # 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" [ -z "$new_node" ] && new_node="$first_node"
msg="切换到$([ "$now_node" = "$main_node" ] && echo 备用节点 || echo 下一个备用节点)检测!" local msg2="$($APP_FILE i18n "next backup node")"
[ "$now_node" = "$main_node" ] && msg2="$($APP_FILE i18n "backup node")"
msg="$($APP_FILE i18n "switch to %s test detect!" "${msg2}")"
else else
# 只有一个后备节点时,与主节点轮询 # When there is only one backup node, poll with the primary node.
new_node=$([ "$now_node" = "$main_node" ] && echo "$b_nodes" || echo "$main_node") new_node=$([ "$now_node" = "$main_node" ] && echo "$b_nodes" || echo "$main_node")
msg="切换到$([ "$now_node" = "$main_node" ] && echo 备用节点 || echo 主节点)检测!" local msg2="$($APP_FILE i18n "main node")"
[ "$now_node" = "$main_node" ] && msg2="$($APP_FILE i18n "backup node")"
msg="$($APP_FILE i18n "switch to %s test detect!" "${msg2}")"
fi fi
echolog "Socks切换检测:${id}$(config_n_get $now_node type)[$(config_n_get $now_node remarks)]】异常,$msg" $APP_FILE 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} test_node ${new_node}
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
# [ "$restore_switch" = "0" ] && { # [ "$restore_switch" = "0" ] && {
@@ -143,10 +141,10 @@ test_auto_switch() {
# [ -z "$(echo $b_nodes | grep $main_node)" ] && uci add_list $CONFIG.${id}.autoswitch_backup_node=$main_node # [ -z "$(echo $b_nodes | grep $main_node)" ] && uci add_list $CONFIG.${id}.autoswitch_backup_node=$main_node
# uci commit $CONFIG # uci commit $CONFIG
# } # }
echolog "Socks切换检测:${id}$(config_n_get $new_node type)[$(config_n_get $new_node remarks)]】正常,切换到此节点!" $APP_FILE 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)"
/usr/share/${CONFIG}/app.sh socks_node_switch flag=${id} new_node=${new_node} $APP_FILE socks_node_switch flag=${id} new_node=${new_node}
[ $? -eq 0 ] && { [ $? -eq 0 ] && {
echolog "Socks切换检测${id}节点切换完毕!" $APP_FILE log_i18n 0 "Socks switch detection: %s node switch complete!" "${id}"
} }
return 0 return 0
else else
@@ -181,7 +179,6 @@ start() {
continue continue
} }
pgrep -af "${CONFIG}/" | awk '/app\.sh.*(start|stop)/ || /nftables\.sh/ || /iptables\.sh/ { found = 1 } END { exit !found }' && { pgrep -af "${CONFIG}/" | awk '/app\.sh.*(start|stop)/ || /nftables\.sh/ || /iptables\.sh/ { found = 1 } END { exit !found }' && {
# 特定任务执行中不检测
sleep 6s sleep 6s
continue continue
} }

View File

@@ -19,6 +19,7 @@ local jsonParse, jsonStringify = luci.jsonc.parse, luci.jsonc.stringify
local base64Decode = api.base64Decode local base64Decode = api.base64Decode
local uci = api.uci local uci = api.uci
local fs = api.fs local fs = api.fs
local i18n = api.i18n
uci:revert(appname) uci:revert(appname)
local has_ss = api.is_finded("ss-redir") local has_ss = api.is_finded("ss-redir")
@@ -28,7 +29,7 @@ local has_singbox = api.finded_com("sing-box")
local has_xray = api.finded_com("xray") local has_xray = api.finded_com("xray")
local has_hysteria2 = api.finded_com("hysteria") local has_hysteria2 = api.finded_com("hysteria")
local allowInsecure_default = true local allowInsecure_default = true
-- 取节点使用core类型节点订阅页面未设置时自动取默认 -- Nodes should be retrieved using the core type (if not set on the node subscription page, the default type will be used automatically).
local function get_core(field, candidates) local function get_core(field, candidates)
local v = uci:get(appname, "@global_subscribe[0]", field) local v = uci:get(appname, "@global_subscribe[0]", field)
if not v or v == "" then if not v or v == "" then
@@ -43,11 +44,11 @@ local trojan_type_default = get_core("trojan_type", {{has_singbox,"sing-box"},{h
local vmess_type_default = get_core("vmess_type", {{has_xray,"xray"},{has_singbox,"sing-box"}}) local vmess_type_default = get_core("vmess_type", {{has_xray,"xray"},{has_singbox,"sing-box"}})
local vless_type_default = get_core("vless_type", {{has_xray,"xray"},{has_singbox,"sing-box"}}) local vless_type_default = get_core("vless_type", {{has_xray,"xray"},{has_singbox,"sing-box"}})
local hysteria2_type_default = get_core("hysteria2_type", {{has_hysteria2,"hysteria2"},{has_singbox,"sing-box"}}) local hysteria2_type_default = get_core("hysteria2_type", {{has_hysteria2,"hysteria2"},{has_singbox,"sing-box"}})
----
local domain_strategy_default = uci:get(appname, "@global_subscribe[0]", "domain_strategy") or "" local domain_strategy_default = uci:get(appname, "@global_subscribe[0]", "domain_strategy") or ""
local domain_strategy_node = "" local domain_strategy_node = ""
local preproxy_node_group, to_node_group, chain_node_type = "", "", "" local preproxy_node_group, to_node_group, chain_node_type = "", "", ""
-- 判断是否过滤节点关键字 -- Determine whether to filter node keywords
local filter_keyword_mode_default = uci:get(appname, "@global_subscribe[0]", "filter_keyword_mode") or "0" local filter_keyword_mode_default = uci:get(appname, "@global_subscribe[0]", "filter_keyword_mode") or "0"
local filter_keyword_discard_list_default = uci:get(appname, "@global_subscribe[0]", "filter_discard_list") or {} local filter_keyword_discard_list_default = uci:get(appname, "@global_subscribe[0]", "filter_discard_list") or {}
local filter_keyword_keep_list_default = uci:get(appname, "@global_subscribe[0]", "filter_keep_list") or {} local filter_keyword_keep_list_default = uci:get(appname, "@global_subscribe[0]", "filter_keep_list") or {}
@@ -115,7 +116,7 @@ for k, e in ipairs(api.get_valid_nodes()) do
end end
end end
-- 获取各项动态配置的当前服务器,可以用 get 和 set get必须要获取到节点表 -- To retrieve the current server's dynamic configurations, you can use `get` and `set`. `get` requires access to the node table.
local CONFIG = {} local CONFIG = {}
do do
if true then if true then
@@ -125,7 +126,7 @@ do
local node_id = uci:get(appname, szType, option) local node_id = uci:get(appname, szType, option)
CONFIG[#CONFIG + 1] = { CONFIG[#CONFIG + 1] = {
log = true, log = true,
remarks = "节点", remarks = i18n.translatef("Node"),
currentNode = node_id and uci:get_all(appname, node_id) or nil, currentNode = node_id and uci:get_all(appname, node_id) or nil,
set = function(o, server) set = function(o, server)
uci:set(appname, szType, option, server) uci:set(appname, szType, option, server)
@@ -144,7 +145,7 @@ do
CONFIG[#CONFIG + 1] = { CONFIG[#CONFIG + 1] = {
log = true, log = true,
id = id, id = id,
remarks = "Socks节点列表[" .. i .. "]", remarks = i18n.translatef("Socks node list [%s]", i),
currentNode = node_id and uci:get_all(appname, node_id) or nil, currentNode = node_id and uci:get_all(appname, node_id) or nil,
set = function(o, server) set = function(o, server)
if not server or server == "" then if not server or server == "" then
@@ -157,7 +158,7 @@ do
end end
} }
if t.autoswitch_backup_node and #t.autoswitch_backup_node > 0 then if t.autoswitch_backup_node and #t.autoswitch_backup_node > 0 then
local flag = "Socks节点列表[" .. i .. "]备用节点的列表" local flag = i18n.translatef("Socks node list [%s]", i) .. " " .. i18n.translatef("Backup node list")
local currentNodes = {} local currentNodes = {}
local newNodes = {} local newNodes = {}
for k, node_id in ipairs(t.autoswitch_backup_node) do for k, node_id in ipairs(t.autoswitch_backup_node) do
@@ -206,17 +207,17 @@ do
CONFIG[#CONFIG + 1] = { CONFIG[#CONFIG + 1] = {
log = true, log = true,
id = t[".name"], id = t[".name"],
remarks = "HAProxy负载均衡节点列表[" .. i .. "]", remarks = i18n.translatef("HAProxy node list [%s]", i),
currentNode = node_id and uci:get_all(appname, node_id) or nil, currentNode = node_id and uci:get_all(appname, node_id) or nil,
set = function(o, server) set = function(o, server)
-- 如果当前 lbss 值不是 ip:port 格式,才进行修改 -- Modify the LBS value only if it is not in IP:Port format.
if not is_ip_port(t[option]) then if not is_ip_port(t[option]) then
uci:set(appname, t[".name"], option, server) uci:set(appname, t[".name"], option, server)
o.newNodeId = server o.newNodeId = server
end end
end, end,
delete = function(o) delete = function(o)
-- 如果当前 lbss 值不是 ip:port 格式,才进行删除 -- Deletion is only performed if the current LBS value is not in IP:port format.
if not is_ip_port(t[option]) then if not is_ip_port(t[option]) then
uci:delete(appname, t[".name"]) uci:delete(appname, t[".name"])
end end
@@ -234,7 +235,7 @@ do
CONFIG[#CONFIG + 1] = { CONFIG[#CONFIG + 1] = {
log = true, log = true,
id = t[".name"], id = t[".name"],
remarks = "访问控制列表[" .. i .. "]", remarks = i18n.translatef("ACL list [%s]", i),
currentNode = node_id and uci:get_all(appname, node_id) or nil, currentNode = node_id and uci:get_all(appname, node_id) or nil,
set = function(o, server) set = function(o, server)
uci:set(appname, t[".name"], option, server) uci:set(appname, t[".name"], option, server)
@@ -255,11 +256,11 @@ do
end) end)
table.insert(rules, { table.insert(rules, {
[".name"] = "default_node", [".name"] = "default_node",
remarks = "默认" remarks = i18n.translatef("Default")
}) })
table.insert(rules, { table.insert(rules, {
[".name"] = "main_node", [".name"] = "main_node",
remarks = "默认前置" remarks = i18n.translatef("Default Preproxy")
}) })
for k, e in pairs(rules) do for k, e in pairs(rules) do
@@ -269,7 +270,7 @@ do
CONFIG[#CONFIG + 1] = { CONFIG[#CONFIG + 1] = {
log = false, log = false,
currentNode = _node_id and uci:get_all(appname, _node_id) or nil, currentNode = _node_id and uci:get_all(appname, _node_id) or nil,
remarks = "分流" .. e.remarks .. "节点", remarks = i18n.translatef("Shunt [%s] node", e.remarks),
set = function(o, server) set = function(o, server)
if not server then server = "" end if not server then server = "" end
uci:set(appname, node_id, e[".name"], server) uci:set(appname, node_id, e[".name"], server)
@@ -280,7 +281,7 @@ do
end end
elseif node.protocol and node.protocol == '_balancing' then elseif node.protocol and node.protocol == '_balancing' then
local flag = "Xray负载均衡节点[" .. node_id .. "]列表" local flag = i18n.translatef("Xray Load Balancing node [%s] list", node_id)
local currentNodes = {} local currentNodes = {}
local newNodes = {} local newNodes = {}
if node.balancing_node then if node.balancing_node then
@@ -310,13 +311,13 @@ do
end end
} }
--后备节点 -- Backup Node
local currentNode = uci:get_all(appname, node_id) or nil local currentNode = uci:get_all(appname, node_id) or nil
if currentNode and currentNode.fallback_node then if currentNode and currentNode.fallback_node then
CONFIG[#CONFIG + 1] = { CONFIG[#CONFIG + 1] = {
log = true, log = true,
id = node_id, id = node_id,
remarks = "Xray负载均衡节点[" .. node_id .. "]后备节点", remarks = i18n.translatef("Xray Load Balancing node [%s] backup node", node_id),
currentNode = uci:get_all(appname, currentNode.fallback_node) or nil, currentNode = uci:get_all(appname, currentNode.fallback_node) or nil,
set = function(o, server) set = function(o, server)
uci:set(appname, node_id, "fallback_node", server) uci:set(appname, node_id, "fallback_node", server)
@@ -328,7 +329,7 @@ do
} }
end end
elseif node.protocol and node.protocol == '_urltest' then elseif node.protocol and node.protocol == '_urltest' then
local flag = "Sing-Box URLTest节点[" .. node_id .. "]列表" local flag = i18n.translatef("Sing-Box URLTest node [%s] list", node_id)
local currentNodes = {} local currentNodes = {}
local newNodes = {} local newNodes = {}
if node.urltest_node then if node.urltest_node then
@@ -358,13 +359,13 @@ do
end end
} }
else else
--前置代理节点 -- Preproxy Node
local currentNode = uci:get_all(appname, node_id) or nil local currentNode = uci:get_all(appname, node_id) or nil
if currentNode and currentNode.preproxy_node then if currentNode and currentNode.preproxy_node then
CONFIG[#CONFIG + 1] = { CONFIG[#CONFIG + 1] = {
log = true, log = true,
id = node_id, id = node_id,
remarks = "节点[" .. node_id .. "]前置代理节点", remarks = i18n.translatef("Node [%s] preproxy node", node_id),
currentNode = uci:get_all(appname, currentNode.preproxy_node) or nil, currentNode = uci:get_all(appname, currentNode.preproxy_node) or nil,
set = function(o, server) set = function(o, server)
uci:set(appname, node_id, "preproxy_node", server) uci:set(appname, node_id, "preproxy_node", server)
@@ -375,13 +376,13 @@ do
end end
} }
end end
--落地节点 -- Landing node
local currentNode = uci:get_all(appname, node_id) or nil local currentNode = uci:get_all(appname, node_id) or nil
if currentNode and currentNode.to_node then if currentNode and currentNode.to_node then
CONFIG[#CONFIG + 1] = { CONFIG[#CONFIG + 1] = {
log = true, log = true,
id = node_id, id = node_id,
remarks = "节点[" .. node_id .. "]落地节点", remarks = i18n.translatef("Node [%s] landing node", node_id),
currentNode = uci:get_all(appname, currentNode.to_node) or nil, currentNode = uci:get_all(appname, currentNode.to_node) or nil,
set = function(o, server) set = function(o, server)
uci:set(appname, node_id, "to_node", server) uci:set(appname, node_id, "to_node", server)
@@ -425,7 +426,7 @@ local function UrlDecode(szText)
end) or nil end) or nil
end end
-- 取机场信息(剩余流量、到期时间) -- Retrieve subscribe information (remaining data allowance, expiration time).
local subscribe_info = {} local subscribe_info = {}
local function get_subscribe_info(cfgid, value) local function get_subscribe_info(cfgid, value)
if type(cfgid) ~= "string" or cfgid == "" or type(value) ~= "string" then if type(cfgid) ~= "string" or cfgid == "" or type(value) ~= "string" then
@@ -443,7 +444,7 @@ local function get_subscribe_info(cfgid, value)
end end
end end
-- 设置 ss 协议实现类型 -- Configure the SS protocol implementation type
local function set_ss_implementation(result) local function set_ss_implementation(result)
if ss_type_default == "shadowsocks-libev" and has_ss then if ss_type_default == "shadowsocks-libev" and has_ss then
result.type = "SS" result.type = "SS"
@@ -457,24 +458,24 @@ local function set_ss_implementation(result)
result.type = 'sing-box' result.type = 'sing-box'
result.protocol = 'shadowsocks' result.protocol = 'shadowsocks'
else else
log("跳过 SS 节点,因未适配到 SS 核心程序,或未正确设置节点使用类型。") log(i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", "SS", "SS"))
return nil return nil
end end
return result return result
end end
-- 处理数据 -- Processing data
local function processData(szType, content, add_mode, group) local function processData(szType, content, add_mode, group)
--log(content, add_mode, group) --log(content, add_mode, group)
local result = { local result = {
timeout = 60, timeout = 60,
add_mode = add_mode, --0为手动配置,1为导入,2为订阅 add_mode = add_mode, -- `0` for manual configuration, `1` for import, `2` for subscription
group = group group = group
} }
--ssr://base64(host:port:protocol:method:obfs:base64pass/?obfsparam=base64param&protoparam=base64param&remarks=base64remarks&group=base64group&udpport=0&uot=0) --ssr://base64(host:port:protocol:method:obfs:base64pass/?obfsparam=base64param&protoparam=base64param&remarks=base64remarks&group=base64group&udpport=0&uot=0)
if szType == 'ssr' then if szType == 'ssr' then
if not has_ssr then if not has_ssr then
log("跳过 SSR 节点,因未安装 SSR 核心程序 shadowsocksr-libev") log(i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", "SSR", "shadowsocksr-libev"))
return nil return nil
end end
result.type = "SSR" result.type = "SSR"
@@ -508,7 +509,7 @@ local function processData(szType, content, add_mode, group)
elseif vmess_type_default == "xray" and has_xray then elseif vmess_type_default == "xray" and has_xray then
result.type = "Xray" result.type = "Xray"
else else
log("跳过 VMess 节点,因未适配到 VMess 核心程序,或未正确设置节点使用类型。") log(i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", "VMess", "VMess"))
return nil return nil
end end
result.alter_id = info.aid result.alter_id = info.aid
@@ -622,7 +623,7 @@ local function processData(szType, content, add_mode, group)
end end
if result.type == "sing-box" and (result.transport == "mkcp" or result.transport == "xhttp") then if result.type == "sing-box" and (result.transport == "mkcp" or result.transport == "xhttp") then
log("跳过节点:" .. result.remarks .."因Sing-Box不支持" .. szType .. "协议的" .. result.transport .. "传输方式需更换Xray。") log(i18n.translatef("Skip node: %s. Because Sing-Box does not support the %s protocol's %s transmission method, Xray needs to be used instead.", result.remarks, szType, result.transport))
return nil return nil
end end
elseif szType == "ss" then elseif szType == "ss" then
@@ -700,10 +701,10 @@ local function processData(szType, content, add_mode, group)
method = userinfo:sub(1, userinfo:find(":") - 1) method = userinfo:sub(1, userinfo:find(":") - 1)
password = userinfo:sub(userinfo:find(":") + 1, #userinfo) password = userinfo:sub(userinfo:find(":") + 1, #userinfo)
else else
password = hostInfo[1] --一些链接用明文uuid做密码 password = hostInfo[1] -- Some links use plaintext UUIDs as passwords.
end end
-- 判断密码是否经过url编码 -- Determine if the password is URL encoded
local function isURLEncodedPassword(pwd) local function isURLEncodedPassword(pwd)
if not pwd:find("%%[0-9A-Fa-f][0-9A-Fa-f]") then if not pwd:find("%%[0-9A-Fa-f][0-9A-Fa-f]") then
return false return false
@@ -734,14 +735,14 @@ local function processData(szType, content, add_mode, group)
if result.plugin then if result.plugin then
if result.type == 'Xray' then if result.type == 'Xray' then
-- obfs-local插件转换成xray支持的格式 -- The obfs-local plugin converts data to a format supported by xray.
if result.plugin ~= "obfs-local" then if result.plugin ~= "obfs-local" then
result.error_msg = "Xray不支持 " .. result.plugin .. " 插件." result.error_msg = i18n.translatef("Xray unsupport %s plugin.", result.plugin)
else else
local obfs = result.plugin_opts:match("obfs=([^;]+)") or "" local obfs = result.plugin_opts:match("obfs=([^;]+)") or ""
local obfs_host = result.plugin_opts:match("obfs%-host=([^;]+)") or "" local obfs_host = result.plugin_opts:match("obfs%-host=([^;]+)") or ""
if obfs == "" or obfs_host == "" then if obfs == "" or obfs_host == "" then
result.error_msg = "SS " .. result.plugin .. " 插件选项不完整." result.error_msg = "SS " .. result.plugin .. " " .. i18n.translatef("Plugin options Incomplete.")
end end
if obfs == "http" then if obfs == "http" then
result.transport = "raw" result.transport = "raw"
@@ -770,8 +771,8 @@ local function processData(szType, content, add_mode, group)
end end
end end
if aead2022 then if aead2022 then
-- shadowsocks-libev 不支持2022加密 -- shadowsocks-libev does not support 2022 encryption.
result.error_msg = "shadowsocks-libev 不支持2022加密." result.error_msg = i18n.translatef("shadowsocks-libev unsupport 2022 encryption.")
end end
end end
@@ -875,15 +876,15 @@ local function processData(szType, content, add_mode, group)
result.tls_allowInsecure = allowInsecure_default and "1" or "0" result.tls_allowInsecure = allowInsecure_default and "1" or "0"
end end
else else
result.error_msg = "请更换Xray或Sing-Box来支持SS更多的传输方式." result.error_msg = i18n.translatef("Please replace Xray or Sing-Box to support more transmission methods in Shadowsocks.")
end end
end end
if params["shadow-tls"] then if params["shadow-tls"] then
if result.type ~= "sing-box" and result.type ~= "SS-Rust" then if result.type ~= "sing-box" and result.type ~= "SS-Rust" then
result.error_msg = ss_type_default .. " 不支持 shadow-tls 插件." result.error_msg = ss_type_default .. " " .. i18n.translatef("unsupport %s plugin.", "shadow-tls")
else else
-- 解析SS Shadow-TLS 插件参数 -- Parsing SS Shadow-TLS plugin parameters
local function parseShadowTLSParams(b64str, out) local function parseShadowTLSParams(b64str, out)
local ok, data = pcall(jsonParse, base64Decode(b64str)) local ok, data = pcall(jsonParse, base64Decode(b64str))
if not ok or type(data) ~= "table" then return "" end if not ok or type(data) ~= "table" then return "" end
@@ -930,7 +931,7 @@ local function processData(szType, content, add_mode, group)
result.type = 'Xray' result.type = 'Xray'
result.protocol = 'trojan' result.protocol = 'trojan'
else else
log("跳过 Trojan 节点,因未适配到 Trojan 核心程序,或未正确设置节点使用类型。") log(i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", "Trojan", "Trojan"))
return nil return nil
end end
@@ -990,7 +991,6 @@ local function processData(szType, content, add_mode, group)
else else
result.tls_allowInsecure = string.lower(params.allowinsecure) == "true" and "1" or "0" result.tls_allowInsecure = string.lower(params.allowinsecure) == "true" and "1" or "0"
end end
--log(result.remarks .. ' 使用节点AllowInsecure设定: '.. result.tls_allowInsecure)
else else
result.tls_allowInsecure = allowInsecure_default and "1" or "0" result.tls_allowInsecure = allowInsecure_default and "1" or "0"
end end
@@ -1077,7 +1077,7 @@ local function processData(szType, content, add_mode, group)
result.alpn = params.alpn result.alpn = params.alpn
if result.type == "sing-box" and (result.transport == "mkcp" or result.transport == "xhttp") then if result.type == "sing-box" and (result.transport == "mkcp" or result.transport == "xhttp") then
log("跳过节点:" .. result.remarks .."因Sing-Box不支持" .. szType .. "协议的" .. result.transport .. "传输方式需更换Xray。") log(i18n.translatef("Skip node: %s. Because Sing-Box does not support the %s protocol's %s transmission method, Xray needs to be used instead.", result.remarks, szType, result.transport))
return nil return nil
end end
end end
@@ -1098,7 +1098,7 @@ local function processData(szType, content, add_mode, group)
elseif vless_type_default == "xray" and has_xray then elseif vless_type_default == "xray" and has_xray then
result.type = "Xray" result.type = "Xray"
else else
log("跳过 VLESS 节点,因未适配到 VLESS 核心程序,或未正确设置节点使用类型。") log(i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", "VLESS", "VLESS"))
return nil return nil
end end
result.protocol = "vless" result.protocol = "vless"
@@ -1264,7 +1264,7 @@ local function processData(szType, content, add_mode, group)
end end
if result.type == "sing-box" and (result.transport == "mkcp" or result.transport == "xhttp") then if result.type == "sing-box" and (result.transport == "mkcp" or result.transport == "xhttp") then
log("跳过节点:" .. result.remarks .."因Sing-Box不支持" .. szType .. "协议的" .. result.transport .. "传输方式需更换Xray。") log(i18n.translatef("Skip node: %s. Because Sing-Box does not support the %s protocol's %s transmission method, Xray needs to be used instead.", result.remarks, szType, result.transport))
return nil return nil
end end
end end
@@ -1273,7 +1273,7 @@ local function processData(szType, content, add_mode, group)
result.type = 'sing-box' result.type = 'sing-box'
result.protocol = "hysteria" result.protocol = "hysteria"
else else
log("跳过 Hysteria 节点,因未安装 Hysteria 核心程序 Sing-box") log(i18n.translatef("Skip the %s node because the %s core program is not installed.", "Hysteria", "Hysteria", "Sing-Box"))
return nil return nil
end end
@@ -1314,7 +1314,6 @@ local function processData(szType, content, add_mode, group)
params.allowinsecure = params.allowinsecure or params.insecure params.allowinsecure = params.allowinsecure or params.insecure
if params.allowinsecure and (params.allowinsecure == "1" or params.allowinsecure == "0") then if params.allowinsecure and (params.allowinsecure == "1" or params.allowinsecure == "0") then
result.tls_allowInsecure = params.allowinsecure result.tls_allowInsecure = params.allowinsecure
--log(result.remarks ..' 使用节点AllowInsecure设定: '.. result.tls_allowInsecure)
else else
result.tls_allowInsecure = allowInsecure_default and "1" or "0" result.tls_allowInsecure = allowInsecure_default and "1" or "0"
end end
@@ -1363,7 +1362,6 @@ local function processData(szType, content, add_mode, group)
params.allowinsecure = params.allowinsecure or params.insecure params.allowinsecure = params.allowinsecure or params.insecure
if params.allowinsecure and (params.allowinsecure == "1" or params.allowinsecure == "0") then if params.allowinsecure and (params.allowinsecure == "1" or params.allowinsecure == "0") then
result.tls_allowInsecure = params.allowinsecure result.tls_allowInsecure = params.allowinsecure
--log(result.remarks ..' 使用节点AllowInsecure设定: '.. result.tls_allowInsecure)
else else
result.tls_allowInsecure = allowInsecure_default and "1" or "0" result.tls_allowInsecure = allowInsecure_default and "1" or "0"
end end
@@ -1383,7 +1381,7 @@ local function processData(szType, content, add_mode, group)
result.hysteria2_obfs = params["obfs-password"] or params["obfs_password"] result.hysteria2_obfs = params["obfs-password"] or params["obfs_password"]
end end
else else
log("跳过 Hysteria2 节点,因未适配到 Hysteria2 核心程序,或未正确设置节点使用类型。") log(i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", "Hysteria2", "Hysteria2"))
return nil return nil
end end
elseif szType == 'tuic' then elseif szType == 'tuic' then
@@ -1391,7 +1389,7 @@ local function processData(szType, content, add_mode, group)
result.type = 'sing-box' result.type = 'sing-box'
result.protocol = "tuic" result.protocol = "tuic"
else else
log("跳过 Tuic 节点,因未安装 Tuic 核心程序 Sing-box") log(i18n.translatef("Skip the %s node because the %s core program is not installed.", "Tuic", "Tuic", "Sing-Box"))
return nil return nil
end end
@@ -1443,7 +1441,6 @@ local function processData(szType, content, add_mode, group)
else else
result.tls_allowInsecure = string.lower(params.allowinsecure) == "true" and "1" or "0" result.tls_allowInsecure = string.lower(params.allowinsecure) == "true" and "1" or "0"
end end
--log(result.remarks .. ' 使用节点AllowInsecure设定: '.. result.tls_allowInsecure)
else else
result.tls_allowInsecure = allowInsecure_default and "1" or "0" result.tls_allowInsecure = allowInsecure_default and "1" or "0"
end end
@@ -1452,7 +1449,7 @@ local function processData(szType, content, add_mode, group)
result.type = 'sing-box' result.type = 'sing-box'
result.protocol = "anytls" result.protocol = "anytls"
else else
log("跳过 AnyTLS 节点,因未安装 AnyTLS 核心程序 Sing-box 1.12") log(i18n.translatef("Skip the %s node because the %s core program is not installed.", "AnyTLS", "AnyTLS", "Sing-Box 1.12"))
return nil return nil
end end
@@ -1516,12 +1513,12 @@ local function processData(szType, content, add_mode, group)
local singbox_version = api.get_app_version("sing-box") local singbox_version = api.get_app_version("sing-box")
local version_ge_1_12 = api.compare_versions(singbox_version:match("[^v]+"), ">=", "1.12.0") local version_ge_1_12 = api.compare_versions(singbox_version:match("[^v]+"), ">=", "1.12.0")
if not has_singbox or not version_ge_1_12 then if not has_singbox or not version_ge_1_12 then
log("跳过节点:" .. result.remarks ..",因" .. szType .. "类型的节点需要 Sing-Box 1.12 以上版本支持。") log(i18n.translatef("Skip the %s node, as %s type nodes require Sing-Box version 1.12 or higher.", result.remarks, szType))
return nil return nil
end end
end end
else else
log('暂时不支持' .. szType .. "类型的节点订阅,跳过此节点。") log(i18n.translatef("%s type node subscriptions are not currently supported, skip this node.", szType))
return nil return nil
end end
if not result.remarks or result.remarks == "" then if not result.remarks or result.remarks == "" then
@@ -1559,7 +1556,7 @@ local function truncate_nodes(group)
local removeNodesSet = {} local removeNodesSet = {}
for k, v in pairs(config.currentNodes) do for k, v in pairs(config.currentNodes) do
if v.currentNode and v.currentNode.add_mode == "2" then if v.currentNode and v.currentNode.add_mode == "2" then
if (not group) or (group and group == v.currentNode.group) then if (not group) or (group:lower() == (v.currentNode.group or ""):lower()) then
removeNodesSet[v.currentNode[".name"]] = true removeNodesSet[v.currentNode[".name"]] = true
end end
end end
@@ -1574,7 +1571,7 @@ local function truncate_nodes(group)
end end
else else
if config.currentNode and config.currentNode.add_mode == "2" then if config.currentNode and config.currentNode.add_mode == "2" then
if (not group) or (group and group == config.currentNode.group) then if (not group) or (group:lower() == (config.currentNode.group or ""):lower()) then
if config.delete then if config.delete then
config.delete(config) config.delete(config)
elseif config.set then elseif config.set then
@@ -1586,13 +1583,13 @@ local function truncate_nodes(group)
end end
uci:foreach(appname, "nodes", function(node) uci:foreach(appname, "nodes", function(node)
if node.add_mode == "2" then if node.add_mode == "2" then
if (not group) or (group and group == node.group) then if (not group) or (group:lower() == (node.group or ""):lower()) then
uci:delete(appname, node['.name']) uci:delete(appname, node['.name'])
end end
end end
end) end)
uci:foreach(appname, "subscribe_list", function(o) uci:foreach(appname, "subscribe_list", function(o)
if (not group) or group == o.remark then if (not group) or (group:lower() == (o.remark or ""):lower()) then
uci:delete(appname, o['.name'], "md5") uci:delete(appname, o['.name'], "md5")
end end
end) end)
@@ -1602,26 +1599,26 @@ end
local function select_node(nodes, config, parentConfig) local function select_node(nodes, config, parentConfig)
if config.currentNode then if config.currentNode then
local server local server
-- 特别优先级 cfgid -- Special priority: cfgid
if config.currentNode[".name"] then if config.currentNode[".name"] then
for index, node in pairs(nodes) do for index, node in pairs(nodes) do
if node[".name"] == config.currentNode[".name"] then if node[".name"] == config.currentNode[".name"] then
if config.log == nil or config.log == true then if config.log == nil or config.log == true then
log('更新【' .. config.remarks .. '】匹配节点:' .. node.remarks) log(i18n.translatef("Update [%s]", config.remarks) .. " " .. i18n.translatef("Matching node:") .. " " .. node.remarks)
end end
server = node[".name"] server = node[".name"]
break break
end end
end end
end end
-- 第一优先级 类型 + 备注 + IP + 端口 -- First priority: Type + Notes + IP + Port
if not server then if not server then
for index, node in pairs(nodes) do for index, node in pairs(nodes) do
if config.currentNode.type and config.currentNode.remarks and config.currentNode.address and config.currentNode.port then if config.currentNode.type and config.currentNode.remarks and config.currentNode.address and config.currentNode.port then
if node.type and node.remarks and node.address and node.port then if node.type and node.remarks and node.address and node.port then
if node.type == config.currentNode.type and node.remarks == config.currentNode.remarks and (node.address .. ':' .. node.port == config.currentNode.address .. ':' .. config.currentNode.port) then if node.type == config.currentNode.type and node.remarks == config.currentNode.remarks and (node.address .. ':' .. node.port == config.currentNode.address .. ':' .. config.currentNode.port) then
if config.log == nil or config.log == true then if config.log == nil or config.log == true then
log('更新【' .. config.remarks .. '】第一匹配节点:' .. node.remarks) log(i18n.translatef("Update [%s]", config.remarks) .. " " .. i18n.translatef("First Matching node:") .. " " .. node.remarks)
end end
server = node[".name"] server = node[".name"]
break break
@@ -1630,14 +1627,14 @@ local function select_node(nodes, config, parentConfig)
end end
end end
end end
-- 第二优先级 类型 + IP + 端口 -- Second priority: Type + IP + Port
if not server then if not server then
for index, node in pairs(nodes) do for index, node in pairs(nodes) do
if config.currentNode.type and config.currentNode.address and config.currentNode.port then if config.currentNode.type and config.currentNode.address and config.currentNode.port then
if node.type and node.address and node.port then if node.type and node.address and node.port then
if node.type == config.currentNode.type and (node.address .. ':' .. node.port == config.currentNode.address .. ':' .. config.currentNode.port) then if node.type == config.currentNode.type and (node.address .. ':' .. node.port == config.currentNode.address .. ':' .. config.currentNode.port) then
if config.log == nil or config.log == true then if config.log == nil or config.log == true then
log('更新【' .. config.remarks .. '】第二匹配节点:' .. node.remarks) log(i18n.translatef("Update [%s]", config.remarks) .. " " .. i18n.translatef("Second Matching node:") .. " " .. node.remarks)
end end
server = node[".name"] server = node[".name"]
break break
@@ -1646,14 +1643,14 @@ local function select_node(nodes, config, parentConfig)
end end
end end
end end
-- 第三优先级 IP + 端口 -- Third priority: IP + Port
if not server then if not server then
for index, node in pairs(nodes) do for index, node in pairs(nodes) do
if config.currentNode.address and config.currentNode.port then if config.currentNode.address and config.currentNode.port then
if node.address and node.port then if node.address and node.port then
if node.address .. ':' .. node.port == config.currentNode.address .. ':' .. config.currentNode.port then if node.address .. ':' .. node.port == config.currentNode.address .. ':' .. config.currentNode.port then
if config.log == nil or config.log == true then if config.log == nil or config.log == true then
log('更新【' .. config.remarks .. '】第三匹配节点:' .. node.remarks) log(i18n.translatef("Update [%s]", config.remarks) .. " " .. i18n.translatef("Third Matching node:") .. " " .. node.remarks)
end end
server = node[".name"] server = node[".name"]
break break
@@ -1662,14 +1659,14 @@ local function select_node(nodes, config, parentConfig)
end end
end end
end end
-- 第四优先级 IP -- Fourth priority: IP
if not server then if not server then
for index, node in pairs(nodes) do for index, node in pairs(nodes) do
if config.currentNode.address then if config.currentNode.address then
if node.address then if node.address then
if node.address == config.currentNode.address then if node.address == config.currentNode.address then
if config.log == nil or config.log == true then if config.log == nil or config.log == true then
log('更新【' .. config.remarks .. '】第四匹配节点:' .. node.remarks) log(i18n.translatef("Update [%s]", config.remarks) .. " " .. i18n.translatef("Fourth Matching node:") .. " " .. node.remarks)
end end
server = node[".name"] server = node[".name"]
break break
@@ -1678,14 +1675,14 @@ local function select_node(nodes, config, parentConfig)
end end
end end
end end
-- 第五优先级备注 -- Fifth priority: remarks
if not server then if not server then
for index, node in pairs(nodes) do for index, node in pairs(nodes) do
if config.currentNode.remarks then if config.currentNode.remarks then
if node.remarks then if node.remarks then
if node.remarks == config.currentNode.remarks then if node.remarks == config.currentNode.remarks then
if config.log == nil or config.log == true then if config.log == nil or config.log == true then
log('更新【' .. config.remarks .. '】第五匹配节点:' .. node.remarks) log(i18n.translatef("Update [%s]", config.remarks) .. " " .. i18n.translatef("Fifth Matching node:") .. " " .. node.remarks)
end end
server = node[".name"] server = node[".name"]
break break
@@ -1695,11 +1692,11 @@ local function select_node(nodes, config, parentConfig)
end end
end end
if not parentConfig then if not parentConfig then
-- 还不行 随便找一个 -- If that doesn't work, just find one.
if not server then if not server then
if #nodes_table > 0 then if #nodes_table > 0 then
if config.log == nil or config.log == true then if config.log == nil or config.log == true then
log('' .. config.remarks .. '' .. '无法找到最匹配的节点,当前已更换为:' .. nodes_table[1].remarks) log(i18n.translatef("Update [%s]", config.remarks) .. " " .. i18n.translatef("Unable to find the best matching node, now replaced with:") .. " " .. nodes_table[1].remarks)
end end
server = nodes_table[1][".name"] server = nodes_table[1][".name"]
end end
@@ -1721,19 +1718,19 @@ end
local function update_node(manual) local function update_node(manual)
if next(nodeResult) == nil then if next(nodeResult) == nil then
log("没有可用的节点信息更新。") log(i18n.translatef("No node information updates are available."))
return return
end end
local group = {} local group = {}
for _, v in ipairs(nodeResult) do for _, v in ipairs(nodeResult) do
group[v["remark"]] = true group[v["remark"]:lower()] = true
end end
if manual == 0 and next(group) then if manual == 0 and next(group) then
uci:foreach(appname, "nodes", function(node) uci:foreach(appname, "nodes", function(node)
-- 如果未发现新节点或手动导入的节点就不要删除了... -- Do not delete nodes if no new nodes are found or nodes were manually imported...
if node.add_mode == "2" and (node.group and group[node.group] == true) then if node.add_mode == "2" and (node.group and group[node.group:lower()] == true) then
uci:delete(appname, node['.name']) uci:delete(appname, node['.name'])
end end
end) end)
@@ -1750,11 +1747,11 @@ local function update_node(manual)
if kkk ~= "group" or vvv ~= "default" then if kkk ~= "group" or vvv ~= "default" then
uci:set(appname, cfgid, kkk, vvv) uci:set(appname, cfgid, kkk, vvv)
end end
-- sing-box 域名解析策略 -- Sing-Box Domain Strategy
if kkk == "type" and vvv == "sing-box" then if kkk == "type" and vvv == "sing-box" then
uci:set(appname, cfgid, "domain_strategy", domain_strategy_node) uci:set(appname, cfgid, "domain_strategy", domain_strategy_node)
end end
-- 订阅组链式代理 -- Subscription Group Chain Agent
if chain_node_type ~= "" and kkk == "type" and vvv == chain_node_type then if chain_node_type ~= "" and kkk == "type" and vvv == chain_node_type then
if preproxy_node_group ~="" then if preproxy_node_group ~="" then
uci:set(appname, cfgid, "chain_proxy", "1") uci:set(appname, cfgid, "chain_proxy", "1")
@@ -1768,7 +1765,7 @@ local function update_node(manual)
end end
end end
end end
-- 更新机场信息 -- Update subscription information
for cfgid, info in pairs(subscribe_info) do for cfgid, info in pairs(subscribe_info) do
for key, value in pairs(info) do for key, value in pairs(info) do
if value ~= "" then if value ~= "" then
@@ -1816,7 +1813,7 @@ local function parse_link(raw, add_mode, group, cfgid)
if raw and #raw > 0 then if raw and #raw > 0 then
local nodes, szType local nodes, szType
local node_list = {} local node_list = {}
-- SSD 似乎是这种格式 ssd:// 开头的 -- ssd appear to be in this format, starting with ssd://.
if raw:find('ssd://') then if raw:find('ssd://') then
szType = 'ssd' szType = 'ssd'
local nEnd = select(2, raw:find('ssd://')) local nEnd = select(2, raw:find('ssd://'))
@@ -1829,13 +1826,13 @@ local function parse_link(raw, add_mode, group, cfgid)
password = nodes.password password = nodes.password
} }
local servers = {} local servers = {}
-- SS里面包着 干脆直接这样 -- SS is wrapped inside, so let's just like this.
for _, server in ipairs(nodes.servers) do for _, server in ipairs(nodes.servers) do
tinsert(servers, setmetatable(server, { __index = extra })) tinsert(servers, setmetatable(server, { __index = extra }))
end end
nodes = servers nodes = servers
else else
-- ssd 外的格式 -- Formats other than ssd
if add_mode == "1" then if add_mode == "1" then
nodes = split(raw, "\n") nodes = split(raw, "\n")
else else
@@ -1857,22 +1854,22 @@ local function parse_link(raw, add_mode, group, cfgid)
local link = api.trim(dat[2]:gsub("#.*$", "")) local link = api.trim(dat[2]:gsub("#.*$", ""))
result = processData(dat[1], base64Decode(link), add_mode, group) result = processData(dat[1], base64Decode(link), add_mode, group)
else else
local link = dat[2]:gsub("&amp;", "&"):gsub("%s*#%s*", "#") -- 一些奇葩的链接用"&amp;"当做"&""#"前后带空格 local link = dat[2]:gsub("&amp;", "&"):gsub("%s*#%s*", "#") -- Some odd links use "&" as "&", and include spaces before and after "#".
result = processData(dat[1], link, add_mode, group) result = processData(dat[1], link, add_mode, group)
end end
end end
else else
log('跳过未知类型: ' .. szType) log(i18n.translatef("Skip unknown types:") .. " " .. szType)
end end
-- log(result) -- log(result)
if result then if result then
if result.error_msg then if result.error_msg then
log('丢弃节点: ' .. result.remarks .. ", 原因:" .. result.error_msg) log(i18n.translatef("Discard node: %s, Reason:", result.remarks) .. " " .. result.error_msg)
elseif not result.type then elseif not result.type then
log('丢弃节点: ' .. result.remarks .. ", 找不到可使用二进制.") log(i18n.translatef("Discard node: %s, Reason:", result.remarks) .. " " .. i18n.translatef("No usable binary was found."))
elseif (add_mode == "2" and is_filter_keyword(result.remarks)) or not result.address or result.remarks == "NULL" or result.address == "127.0.0.1" or elseif (add_mode == "2" and is_filter_keyword(result.remarks)) or not result.address or result.remarks == "NULL" or result.address == "127.0.0.1" or
(not datatypes.hostname(result.address) and not (api.is_ip(result.address))) then (not datatypes.hostname(result.address) and not (api.is_ip(result.address))) then
log('丢弃过滤节点: ' .. result.type .. ' 节点, ' .. result.remarks) log(i18n.translatef("Discard filter nodes: %s type node %s", result.type, result.remarks))
else else
tinsert(node_list, result) tinsert(node_list, result)
end end
@@ -1882,7 +1879,7 @@ local function parse_link(raw, add_mode, group, cfgid)
end end
end, function (err) end, function (err)
--log(err) --log(err)
log(v, "解析错误,跳过此节点。") log(v, i18n.translatef("Parsing error, skip this node."))
end end
) )
end end
@@ -1893,10 +1890,10 @@ local function parse_link(raw, add_mode, group, cfgid)
list = node_list list = node_list
} }
end end
log('成功解析【' .. group .. '】节点数量: ' .. #node_list) log(i18n.translatef("Successfully resolved the [%s] node, number: %s", group, #node_list))
else else
if add_mode == "2" then if add_mode == "2" then
log('获取到的【' .. group .. '】订阅内容为空,可能是订阅地址无效,或是网络问题,请诊断!') log(i18n.translatef("Get subscription content for [%s] is empty. This may be due to an invalid subscription address or a network problem. Please diagnose the issue!", group))
end end
end end
end end
@@ -1969,7 +1966,7 @@ local execute = function()
domain_strategy_node = domain_strategy_default domain_strategy_node = domain_strategy_default
end end
-- 订阅组链式代理 -- Subscription Group Chain Agent
local function valid_chain_node(node) local function valid_chain_node(node)
if not node then return "" end if not node then return "" end
local cp = uci:get(appname, node, "chain_proxy") or "" local cp = uci:get(appname, node, "chain_proxy") or ""
@@ -1986,8 +1983,8 @@ local execute = function()
local ua = value.user_agent local ua = value.user_agent
local access_mode = value.access_mode local access_mode = value.access_mode
local result = (not access_mode) and "自动" or (access_mode == "direct" and "直连访问" or (access_mode == "proxy" and "通过代理" or "自动")) local result = (not access_mode) and i18n.translatef("Auto") or (access_mode == "direct" and i18n.translatef("Direct") or (access_mode == "proxy" and i18n.translatef("Proxy") or i18n.translatef("Auto")))
log('正在订阅:' .. remark .. '' .. url .. ' [' .. result .. ']') log(i18n.translatef("Start subscribing: %s", '' .. remark .. '' .. url .. ' [' .. result .. ']'))
local tmp_file = "/tmp/" .. cfgid local tmp_file = "/tmp/" .. cfgid
value.http_code = curl(url, tmp_file, ua, access_mode) value.http_code = curl(url, tmp_file, ua, access_mode)
if value.http_code ~= 200 then if value.http_code ~= 200 then
@@ -2001,7 +1998,7 @@ local execute = function()
local old_md5 = value.md5 or "" local old_md5 = value.md5 or ""
local new_md5 = luci.sys.exec("md5sum " .. tmp_file .. " 2>/dev/null | awk '{print $1}'"):gsub("\n", "") local new_md5 = luci.sys.exec("md5sum " .. tmp_file .. " 2>/dev/null | awk '{print $1}'"):gsub("\n", "")
if not manual_sub and old_md5 == new_md5 then if not manual_sub and old_md5 == new_md5 then
log('订阅:【' .. remark .. '】没有变化,无需更新。') log(i18n.translatef("Subscription: [%s] No changes, no update required.", remark))
else else
parse_link(raw_data, "2", remark, cfgid) parse_link(raw_data, "2", remark, cfgid)
uci:set(appname, cfgid, "md5", new_md5) uci:set(appname, cfgid, "md5", new_md5)
@@ -2024,7 +2021,7 @@ local execute = function()
if #fail_list > 0 then if #fail_list > 0 then
for index, value in ipairs(fail_list) do for index, value in ipairs(fail_list) do
log(string.format('【%s】订阅失败可能是订阅地址无效或是网络问题请诊断[%s]', value.remark, tostring(value.http_code))) log(i18n.translatef("[%s] Subscription failed. This could be due to an invalid subscription address or a network issue. Please diagnose the problem! [%s]", value.remark, tostring(value.http_code)))
end end
end end
update_node(0) update_node(0)
@@ -2033,13 +2030,13 @@ end
if arg[1] then if arg[1] then
if arg[1] == "start" then if arg[1] == "start" then
log('开始订阅...') log(i18n.translatef("Start subscribing..."))
xpcall(execute, function(e) xpcall(execute, function(e)
log(e) log(e)
log(debug.traceback()) log(debug.traceback())
log('发生错误, 正在恢复服务') log(i18n.translatef("Error, restoring service."))
end) end)
log('订阅完毕...\n') log(i18n.translatef("Subscription complete...") .. "\n")
elseif arg[1] == "add" then elseif arg[1] == "add" then
local f = assert(io.open("/tmp/links.conf", 'r')) local f = assert(io.open("/tmp/links.conf", 'r'))
local raw = f:read('*all') local raw = f:read('*all')

View File

@@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh
## 循环更新脚本 ## Loop update script
CONFIG=passwall2 CONFIG=passwall2
APP_PATH=/usr/share/$CONFIG APP_PATH=/usr/share/$CONFIG

View File

@@ -1,13 +1,6 @@
#!/bin/sh #!/bin/sh
CONFIG=passwall2 CONFIG=passwall2
LOG_FILE=/tmp/log/$CONFIG.log
echolog() {
local d="$(date "+%Y-%m-%d %H:%M:%S")"
#echo -e "$d: $1"
echo -e "$d: $1" >> $LOG_FILE
}
config_n_get() { config_n_get() {
local ret=$(uci -q get "${CONFIG}.${1}.${2}" 2>/dev/null) local ret=$(uci -q get "${CONFIG}.${1}.${2}" 2>/dev/null)

View File

@@ -1,7 +1,7 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=bandix PKG_NAME:=bandix
PKG_VERSION:=0.8.1 PKG_VERSION:=0.8.2
PKG_RELEASE:=1 PKG_RELEASE:=1
PKG_LICENSE:=Apache-2.0 PKG_LICENSE:=Apache-2.0
@@ -13,7 +13,7 @@ include $(INCLUDE_DIR)/package.mk
include $(TOPDIR)/feeds/packages/lang/rust/rust-values.mk include $(TOPDIR)/feeds/packages/lang/rust/rust-values.mk
# 二进制文件的文件名和URL # 二进制文件的文件名和URL
RUST_BANDIX_VERSION:=0.8.1 RUST_BANDIX_VERSION:=0.8.2
RUST_BINARY_FILENAME:=bandix-$(RUST_BANDIX_VERSION)-$(RUSTC_TARGET_ARCH).tar.gz RUST_BINARY_FILENAME:=bandix-$(RUST_BANDIX_VERSION)-$(RUSTC_TARGET_ARCH).tar.gz

View File

@@ -7,6 +7,8 @@ config bandix 'general'
option language 'auto' option language 'auto'
option theme 'auto' option theme 'auto'
option log_level 'info'
config bandix 'traffic' config bandix 'traffic'
option enabled '0' option enabled '0'

View File

@@ -16,6 +16,7 @@ start_service() {
local iface local iface
local port local port
local data_dir local data_dir
local log_level
local traffic_enabled local traffic_enabled
local traffic_retention_seconds local traffic_retention_seconds
local traffic_flush_interval_seconds local traffic_flush_interval_seconds
@@ -28,6 +29,7 @@ start_service() {
config_get iface 'general' 'iface' config_get iface 'general' 'iface'
config_get port 'general' 'port' config_get port 'general' 'port'
config_get data_dir 'general' 'data_dir' config_get data_dir 'general' 'data_dir'
config_get log_level 'general' 'log_level'
config_get_bool traffic_enabled 'traffic' 'enabled' config_get_bool traffic_enabled 'traffic' 'enabled'
config_get traffic_retention_seconds 'traffic' 'traffic_retention_seconds' config_get traffic_retention_seconds 'traffic' 'traffic_retention_seconds'
config_get traffic_flush_interval_seconds 'traffic' 'traffic_flush_interval_seconds' config_get traffic_flush_interval_seconds 'traffic' 'traffic_flush_interval_seconds'
@@ -40,6 +42,9 @@ start_service() {
# 构建基础命令行参数 # 构建基础命令行参数
local args="--iface $iface --port $port --data-dir $data_dir" local args="--iface $iface --port $port --data-dir $data_dir"
if [ -n "$log_level" ]; then
args="$args --log-level $log_level"
fi
# 添加流量监控相关参数 # 添加流量监控相关参数
if [ "$traffic_enabled" -eq 1 ]; then if [ "$traffic_enabled" -eq 1 ]; then