🐶 Sync 2026-05-01 20:23:08

This commit is contained in:
github-actions[bot] committed 2026-05-01 20:23:08 +08:00
1 parent c29ff1458e
commit 55924a6165
44 files changed
+2280 -1514

No files matched your search

+2 -2
View File
@@ -5,8 +5,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=ddns-go
PKG_VERSION:=6.16.13
PKG_RELEASE:=11
PKG_VERSION:=6.17.0
PKG_RELEASE:=12
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/jeessy2/ddns-go/tar.gz/v$(PKG_VERSION)?
+2 -2
View File
@@ -1,8 +1,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-mosdns
PKG_VERSION:=1.7.1
PKG_RELEASE:=5
PKG_VERSION:=1.7.2
PKG_RELEASE:=6
LUCI_TITLE:=LuCI Support for mosdns
LUCI_PKGARCH:=all
@@ -356,7 +356,7 @@ return view.extend({
o.default = false;
o = s.taboption('advanced', form.DynamicList, 'ad_source', _('ADblock Source'),
_('When using custom rule sources, please use rule types supported by MosDNS (domain lists).') +
_('When using custom rule sources, please use rule types supported by MosDNS (domain list or AdGuardHome rules).') +
'<br>' +
_('Support for local files, such as: file:///var/mosdns/example.txt'));
o.depends('adblock', '1');
@@ -3,25 +3,80 @@
'require fs';
'require ui';
'require view';
'require rpc';
var callStartUpdate = rpc.declare({
object: 'luci.mosdns',
method: 'start_update',
expect: { '': {} }
});
var callGetUpdateLog = rpc.declare({
object: 'luci.mosdns',
method: 'get_update_log',
expect: { '': {} }
});
return view.extend({
handleUpdate: function () {
ui.showModal(_('Updating...'), [
var logTextarea = E('textarea', {
'class': 'cbi-input-textarea',
'readonly': 'readonly',
'style': 'width: 100%; height: 300px; font-family: monospace; font-size: 12px; margin-top: 10px;',
'placeholder': _('Starting update...')
});
var closeButton = E('button', {
'class': 'btn',
'style': 'display: none;',
'click': ui.hideModal
}, _('Close'));
ui.showModal(_('Updating Database...'), [
E('p', { 'class': 'spinning' }, _('Please wait, this may take a few moments...')),
logTextarea,
E('div', { 'class': 'right' }, [ closeButton ])
]);
return fs.exec('/usr/share/mosdns/mosdns.uc', ['update'])
.then(function (res) {
ui.hideModal();
if (res.code === 0) {
ui.addNotification(null, E('p', _('Update success')), 'info');
} else {
ui.addNotification(null, E('p', res.stderr + '<br />' + res.stdout), 'warn');
ui.addNotification(null, E('p', _('Update failed, Please check the network status')), 'error');
var pollLog = function() {
return callGetUpdateLog().then(function(res) {
if (res && res.log) {
logTextarea.value = res.log;
logTextarea.scrollTop = logTextarea.scrollHeight;
if (res.log.match(/UPDATE_FINISHED/)) {
ui.hideModal();
ui.addNotification(null, E('p', _('Update success')), 'info');
return false;
}
if (res.log.match(/Another update is already in progress/)) {
ui.hideModal();
ui.addNotification(null, E('p', _('Another update is already in progress.')), 'warn');
return false;
}
}
}).catch(function (e) {
ui.hideModal();
ui.addNotification(null, E('p', _('Update failed: %s').format(e.message)), 'error');
return true;
});
};
return callStartUpdate().then(function(res) {
if (res.success) {
var interval = window.setInterval(function() {
pollLog().then(function(continuePolling) {
if (!continuePolling) {
window.clearInterval(interval);
}
});
}, 1000);
} else {
ui.hideModal();
ui.addNotification(null, E('p', res.error || _('Failed to start update.')), 'error');
}
}).catch(function(e) {
ui.hideModal();
ui.addNotification(null, E('p', _('Update failed: %s').format(e.message)), 'error');
});
},
render: function () {
@@ -61,7 +116,7 @@ return view.extend({
o.value('geoip', _('Full'));
o.value('geoip-only-cn-private', _('Little'));
o.rmempty = false;
o.default = 'geoip';
o.default = 'geoip-only-cn-private';
o = s.option(form.Value, 'github_proxy', _('GitHub Proxy'),
_('Update data files with GitHub Proxy, leave blank to disable proxy downloads.'));
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+1
View File
@@ -6,6 +6,7 @@ config mosdns 'config'
option geo_auto_update '0'
option geo_update_week_time '*'
option geo_update_day_time '2'
option geoip_type 'geoip-only-cn-private'
option configfile '/var/etc/mosdns.json'
option log_level 'info'
option log_file '/var/log/mosdns.log'
+2 -2
View File
@@ -126,7 +126,7 @@ generate_config() {
# plugin: blocklist
json_add_object
json_add_string "tag" "blocklist"
json_add_string "type" "domain_set"
json_add_string "type" "adblock_set"
json_add_object "args"
json_add_array "files"
json_add_string "" "/etc/mosdns/rule/blocklist.txt"
@@ -176,7 +176,7 @@ generate_config() {
# plugin: adlist
json_add_object
json_add_string "tag" "adlist"
json_add_string "type" "domain_set"
json_add_string "type" "adblock_set"
json_add_object "args"
json_add_array "files"
adlist=$($MOSDNS_SCRIPT get_adlist)
@@ -2,7 +2,7 @@
'use strict';
import { popen, mkdir, unlink, writefile, open, stat} from 'fs';
import { popen, mkdir, unlink, writefile, open, stat, stdout } from 'fs';
import { cursor } from 'uci';
import { connect } from 'ubus';
@@ -148,6 +148,7 @@ function update_adlist() {
}
print(`Downloading ${mirror}${url}\n`);
stdout.flush();
let curl_res = exec_sys(`curl --connect-timeout 5 -m 90 --ipv4 -kfSLo "${ad_tmpdir}/${filename}" "${mirror}${url}"`);
if (curl_res.code !== 0) download_failed = true;
}
@@ -191,6 +192,7 @@ function update_geodat() {
let geoip_url = mirror + "https://github.com/Loyalsoldier/geoip/releases/latest/download/" + geoip_type + ".dat";
print(`Downloading ${geoip_url}.sha256sum\n`);
stdout.flush();
if (exec_sys(`curl --connect-timeout 5 -m 20 --ipv4 -kfSLo "${tmpdir}/geoip.dat.sha256sum" "${geoip_url}.sha256sum"`).code !== 0) {
exec_sys(`rm -rf "${tmpdir}"`); exit(1);
}
@@ -203,8 +205,10 @@ function update_geodat() {
if (geoip_sum_local === geoip_sum_remote) {
print("geoip.dat is up to date.\n");
stdout.flush();
} else {
print(`Downloading ${geoip_url}\n`);
stdout.flush();
if (exec_sys(`curl --connect-timeout 5 -m 120 --ipv4 -kfSLo "${tmpdir}/geoip.dat" "${geoip_url}"`).code !== 0) {
exec_sys(`rm -rf "${tmpdir}"`); exit(1);
}
@@ -221,6 +225,7 @@ function update_geodat() {
let geosite_url = mirror + "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat";
print(`Downloading ${geosite_url}.sha256sum\n`);
stdout.flush();
if (exec_sys(`curl --connect-timeout 5 -m 20 --ipv4 -kfSLo "${tmpdir}/geosite.dat.sha256sum" "${geosite_url}.sha256sum"`).code !== 0) {
exec_sys(`rm -rf "${tmpdir}"`); exit(1);
}
@@ -233,8 +238,10 @@ function update_geodat() {
if (geosite_sum_local === geosite_sum_remote) {
print("geosite.dat is up to date.\n");
stdout.flush();
} else {
print(`Downloading ${geosite_url}\n`);
stdout.flush();
if (exec_sys(`curl --connect-timeout 5 -m 120 --ipv4 -kfSLo "${tmpdir}/geosite.dat" "${geosite_url}"`).code !== 0) {
exec_sys(`rm -rf "${tmpdir}"`); exit(1);
}
@@ -313,9 +320,23 @@ switch (action) {
get_adlist();
break;
case "update":
update_geodat();
update_adlist();
v2dat_dump();
if (stat('/var/lock/mosdns_update.lock')) {
print("Another update is already in progress.\n");
exit(1);
}
writefile('/var/lock/mosdns_update.lock', '');
try {
update_geodat();
update_adlist();
v2dat_dump();
print("UPDATE_FINISHED\n");
stdout.flush();
} catch (e) {
print("Update failed: " + e + "\n");
print("UPDATE_FINISHED\n");
stdout.flush();
}
unlink('/var/lock/mosdns_update.lock');
break;
case "update_adlist":
update_adlist();
@@ -18,7 +18,9 @@
"clean_log",
"flush_cache",
"get_version",
"print_log"
"print_log",
"get_update_log",
"start_update"
]
},
"uci": [ "mosdns" ]
@@ -119,6 +119,35 @@ const methods = {
return { error: res.stdout };
}
}
},
start_update: {
call: function() {
try {
if (stat('/var/lock/mosdns_update.lock')) {
return { success: false, error: 'Another update is already in progress.' };
}
exec_sys('echo "" > /var/log/mosdns_update.log');
exec_sys('{ /usr/share/mosdns/mosdns.uc update; rm -f /var/lock/mosdns_update.lock; echo "UPDATE_FINISHED"; } > /var/log/mosdns_update.log 2>&1 &');
return { success: true };
} catch (e) {
return { success: false, error: String(e) };
}
}
},
get_update_log: {
call: function() {
try {
if (stat('/var/log/mosdns_update.log')) {
return { log: readfile('/var/log/mosdns_update.log') };
} else {
return { log: '' };
}
} catch (e) {
return { error: String(e) };
}
}
}
};
+1 -1
View File
@@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall
PKG_VERSION:=26.4.15
PKG_RELEASE:=105
PKG_RELEASE:=106
PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \
@@ -610,7 +610,7 @@ o.validate = function(self, value, t)
local _dns_mode = s.fields["dns_mode"]:formvalue(t)
local _tcp_node = s.fields["tcp_node"]:formvalue(t)
if _dns_mode and _tcp_node then
if (m:get(_tcp_node, "type") or ""):lower() ~= _dns_mode then
if (m:get(_tcp_node, "type") or ""):lower() ~= _dns_mode and not _tcp_node:find("Socks_") then
return nil, translatef("TCP node must be '%s' type to use FakeDNS.", _dns_mode)
end
end
@@ -486,7 +486,7 @@ o.validate = function(self, value, t)
end
local _tcp_node = s.fields["tcp_node"]:formvalue(t)
if _dns_mode and _tcp_node then
if (m:get(_tcp_node, "type") or ""):lower() ~= _dns_mode then
if (m:get(_tcp_node, "type") or ""):lower() ~= _dns_mode and not _tcp_node:find("Socks_") then
return nil, translatef("TCP node must be '%s' type to use FakeDNS.", _dns_mode)
end
end
@@ -1706,8 +1706,7 @@ function gen_config(var)
end
end)
else
local _node = node or node_id
COMMON.default_outbound_tag = gen_outbound_get_tag(flag, _node, nil, {
COMMON.default_outbound_tag = gen_outbound_get_tag(flag, node or node_id, nil, {
fragment = singbox_settings.fragment == "1" or nil,
record_fragment = singbox_settings.record_fragment == "1" or nil,
run_socks_instance = not no_run
@@ -1424,8 +1424,7 @@ function gen_config(var)
rules = rules
}
else
local _node = node or node_id
COMMON.default_outbound_tag = gen_outbound_get_tag(flag, _node, nil, {
COMMON.default_outbound_tag = gen_outbound_get_tag(flag, node or node_id, nil, {
fragment = xray_settings.fragment == "1" or nil,
noise = xray_settings.noise == "1" or nil,
run_socks_instance = not no_run
@@ -1660,8 +1660,17 @@ acl_app() {
set_cache_var "ACL_${sid}_dns_port" "${GLOBAL_DNSMASQ_PORT}"
set_cache_var "ACL_${sid}_tcp_default" "1"
else
local type=$(echo $(config_n_get $tcp_node type) | tr 'A-Z' 'a-z')
local protocol=$(config_n_get $tcp_node protocol)
local type protocol
if [ "$(config_get_type ${tcp_node#Socks_})" = "socks" ]; then
if [ "${dns_mode}" = "xray" ]; then
type="xray"
elif [ "${dns_mode}" = "sing-box" ]; then
type="sing-box"
fi
else
type=$(echo $(config_n_get $tcp_node type) | tr 'A-Z' 'a-z')
protocol=$(config_n_get $tcp_node protocol)
fi
#兼容旧模式,择机移除
[ "$v2ray_dns_mode" = "tcp+doh" ] && v2ray_dns_mode="tcp"
([ "$type" = "sing-box" ] || [ "$type" = "xray" ]) && [ "$protocol" = "_shunt" ] && [ "$type" != "$dns_mode" ] && {
@@ -1743,7 +1752,7 @@ acl_app() {
lua $APP_PATH/helper_dnsmasq.lua add_rule -FLAG ${sid} -TMP_DNSMASQ_PATH ${dnsmasq_conf_path} -DNSMASQ_CONF_FILE ${dnsmasq_conf} \
-LISTEN_PORT ${dnsmasq_port} -DEFAULT_DNS ${DEFAULT_DNS} -LOCAL_DNS $LOCAL_DNS \
-USE_DIRECT_LIST "${use_direct_list}" -USE_PROXY_LIST "${use_proxy_list}" -USE_BLOCK_LIST "${use_block_list}" -USE_GFW_LIST "${use_gfw_list}" -CHN_LIST "${chn_list}" \
-TUN_DNS "127.0.0.1#${_dns_port}" -REMOTE_FAKEDNS 0 -USE_DEFAULT_DNS "${use_default_dns:-direct}" -CHINADNS_DNS ${_china_ng_listen:-0} \
-TUN_DNS "127.0.0.1#${_dns_port}" -REMOTE_FAKEDNS ${remote_fakedns:-0} -USE_DEFAULT_DNS "${use_default_dns:-direct}" -CHINADNS_DNS ${_china_ng_listen:-0} \
-TCP_NODE $tcp_node -DEFAULT_PROXY_MODE ${tcp_proxy_mode} -NO_PROXY_IPV6 ${dnsmasq_filter_proxy_ipv6:-0} -NFTFLAG ${nftflag:-0} \
-NO_LOGIC_LOG 1
ln_run "$(first_type dnsmasq)" "dnsmasq_${sid}" "/dev/null" -C ${dnsmasq_conf} -x ${acl_path}/dnsmasq.pid
@@ -1783,7 +1792,7 @@ acl_app() {
[ "$filter_proxy_ipv6" = "1" ] && remote_dns_query_strategy="UseIPv4"
remote_dns_doh=${remote_dns_doh:-https://1.1.1.1/dns-query}
_extra_param="${_extra_param} dns_listen_port=${_dns_port} remote_dns_protocol=${v2ray_dns_mode} remote_dns_udp_server=${remote_dns} remote_dns_tcp_server=${remote_dns}"
_extra_param="${_extra_param} remote_dns_doh=${remote_dns_doh} remote_dns_query_strategy=${remote_dns_query_strategy} remote_dns_client_ip=${remote_dns_client_ip}"
_extra_param="${_extra_param} remote_dns_doh=${remote_dns_doh} remote_dns_query_strategy=${remote_dns_query_strategy} remote_fakedns=${remote_fakedns:-0} remote_dns_client_ip=${remote_dns_client_ip}"
}
_extra_param="${_extra_param} tcp_proxy_way=$TCP_PROXY_WAY"
[ -n "$udp_node" ] && ([ "$udp_node" = "tcp" ] || [ "$udp_node" = "$tcp_node" ]) && {
@@ -1849,7 +1858,16 @@ acl_app() {
set_cache_var "node_${udp_node}_redir_port" "${redir_port}"
udp_port=$redir_port
local type=$(echo $(config_n_get $udp_node type) | tr 'A-Z' 'a-z')
local type
if [ "$(config_get_type ${udp_node#Socks_})" = "socks" ]; then
if [ "${dns_mode}" = "xray" ]; then
type="xray"
elif [ "${dns_mode}" = "sing-box" ]; then
type="sing-box"
fi
else
type=$(echo $(config_n_get $udp_node type) | tr 'A-Z' 'a-z')
fi
if [ -n "${type}" ] && ([ "${type}" = "sing-box" ] || [ "${type}" = "xray" ]); then
config_file="acl/${udp_node}_UDP_${redir_port}.json"
config_file="$TMP_PATH/$config_file"
+2 -2
View File
@@ -6,8 +6,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall2
PKG_VERSION:=26.4.20
PKG_RELEASE:=40
PKG_VERSION:=26.5.1
PKG_RELEASE:=41
PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \
@@ -61,6 +61,7 @@ end
protocol = s:option(MultiValue, "protocol", translate("Protocol"))
protocol:value("http")
protocol:value("tls")
protocol:value("quic")
protocol:value("bittorrent")
protocol.widget = "checkbox"
protocol.default = nil
@@ -51,7 +51,6 @@ o = s:option(Flag, _n("tls_allowInsecure"), translate("allowInsecure"), translat
o.default = "0"
o = s:option(Value, _n("tls_pinSHA256"), translate("PinSHA256"),translate("Certificate fingerprint"))
o.rewrite_option = o.option
o = s:option(Value, _n("up_mbps"), translate("Max upload Mbps"))
o.rewrite_option = o.option
@@ -374,7 +374,7 @@ if api.compare_versions(os.date("%Y.%m.%d"), "<", "2026.6.1") then
end
if api.compare_versions(xray_version, ">=", "26.1.31") then
o = s:option(Value, _n("tls_CertSha"), translate("TLS Chain Fingerprint (SHA256)"), translate("Once set, connects only when the server’s chain fingerprint matches."))
o = s:option(Value, _n("tls_pinSHA256"), translate("TLS Chain Fingerprint (SHA256)"))
o:depends({ [_n("tls")] = true, [_n("reality")] = false })
o:depends({ [_n("protocol")] = "hysteria2" })
@@ -506,6 +506,11 @@ o:depends({ [_n("transport")] = "mkcp" })
o = s:option(Value, _n("mkcp_domain"), translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain."))
o:depends({ [_n("mkcp_guise")] = "dns" })
o = s:option(Value, _n("mkcp_mtu"), translate("KCP MTU"))
o.datatype = "uinteger"
o.default = 1350
o:depends({ [_n("transport")] = "mkcp" })
o = s:option(Value, _n("mkcp_seed"), translate("KCP Seed"))
o:depends({ [_n("transport")] = "mkcp" })
@@ -356,6 +356,11 @@ o:depends({ [_n("transport")] = "mkcp" })
o = s:option(Value, _n("mkcp_domain"), translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain."))
o:depends({ [_n("mkcp_guise")] = "dns" })
o = s:option(Value, _n("mkcp_mtu"), translate("KCP MTU"))
o.datatype = "uinteger"
o.default = 1350
o:depends({ [_n("transport")] = "mkcp" })
o = s:option(Value, _n("mkcp_seed"), translate("KCP Seed"))
o:depends({ [_n("transport")] = "mkcp" })
+10 -14
View File
@@ -122,21 +122,17 @@ function get_new_port()
end
function exec_call(cmd)
local process = io.popen(cmd .. '; echo -e "\n$?"')
local lines = {}
local result = ""
local return_code
for line in process:lines() do
lines[#lines + 1] = line
math.randomseed(os.time())
local tag = "\x01__RC__" .. tostring(math.random(100000, 999999)) .. "\x01"
local f = io.popen('(' .. cmd .. '); printf "\\n' .. tag .. '%d" "$?"')
local out = f:read("*a") or ""
f:close()
local rc = out:match(tag .. "(%d+)%s*$")
if not rc then
return 255, trim(out)
end
process:close()
if #lines > 0 then
return_code = lines[#lines]
for i = 1, #lines - 1 do
result = result .. lines[i] .. ((i == #lines - 1) and "" or "\n")
end
end
return tonumber(return_code), trim(result)
out = out:gsub("\n?" .. tag .. "%d+%s*$", "")
return tonumber(rc), trim(out)
end
function base64Decode(text)
@@ -93,7 +93,7 @@ function gen_config(var)
tls = {
sni = node.tls_serverName,
insecure = (node.tls_allowInsecure == "1") and true or false,
pinSHA256 = (node.hysteria2_tls_pinSHA256) and node.hysteria2_tls_pinSHA256 or nil,
pinSHA256 = (node.tls_pinSHA256) and node.tls_pinSHA256 or nil,
},
quic = {
initStreamReceiveWindow = (node.hysteria2_recv_window) and tonumber(node.hysteria2_recv_window) or nil,
@@ -173,14 +173,14 @@ function gen_outbound(flag, node, tag, proxy_table)
tlsSettings = (node.stream_security == "tls") and {
serverName = node.tls_serverName,
allowInsecure = (function()
if node.tls_CertSha and node.tls_CertSha ~= "" then return nil end
if node.tls_pinSHA256 and node.tls_pinSHA256 ~= "" then return nil end
if api.compare_versions(os.date("%Y.%m.%d"), "<", "2026.6.1") and node.tls_allowInsecure == "1" then return true end
end)(),
fingerprint = (node.type == "Xray" and node.utls == "1" and node.fingerprint and node.fingerprint ~= "") and node.fingerprint or nil,
pinnedPeerCertSha256 = (function()
if api.compare_versions(xray_version, "<", "26.1.31") then return nil end
if not node.tls_CertSha then return "" end
return node.tls_CertSha
if not node.tls_pinSHA256 then return "" end
return node.tls_pinSHA256
end)(),
verifyPeerCertByName = (function()
if api.compare_versions(xray_version, "<", "26.1.31") then return nil end
@@ -217,7 +217,7 @@ function gen_outbound(flag, node, tag, proxy_table)
}
} or nil,
kcpSettings = (node.transport == "mkcp") and {
mtu = 1350,
mtu = (node.mkcp_mtu and node.mkcp_mtu ~= "") and tonumber(node.mkcp_mtu) or 1350,
tti = 50,
uplinkCapacity = 12,
downlinkCapacity = 100,
@@ -470,7 +470,6 @@ function gen_outbound(flag, node, tag, proxy_table)
finalQuery = true,
disableCache = false,
serveStale = true,
serveExpiredTTL = 30,
}
end
@@ -674,7 +673,7 @@ function gen_config_server(node)
}
} or nil,
kcpSettings = (node.transport == "mkcp") and {
mtu = 1350,
mtu = (node.mkcp_mtu and node.mkcp_mtu ~= "") and tonumber(node.mkcp_mtu) or 1350,
tti = 50,
uplinkCapacity = 12,
downlinkCapacity = 100,
@@ -1639,7 +1638,6 @@ function gen_config(var)
finalQuery = true,
disableCache = false,
serveStale = true,
serveExpiredTTL = 30,
})
end
end
@@ -1738,6 +1736,7 @@ function gen_config(var)
end
end
local dns_block_mode = "host"
dns_block_mode = ""
if dns_block_mode == "host" and dns_outboundTag == "blackhole" then
for d_i, d_k in ipairs(value.domain) do
dns.hosts[d_k] = "0.0.0.0"
@@ -2162,7 +2161,6 @@ function gen_front_dns_config(var)
finalQuery = true,
disableCache = false,
serveStale = true,
serveExpiredTTL = 30,
})
end
end
@@ -16,6 +16,22 @@ local appname = api.appname
display: none !important;
visibility: hidden !important;
}
@media screen and (max-width: 1152px) {
#cbi-<%=appname%>-socks .cbi-section-table-titles {
display: none !important;
}
}
@media screen and (min-width: 1152px) {
#cbi-<%=appname%>-socks input[id*=".<%=appname%>."][id$=".port"],
#cbi-<%=appname%>-socks input[id*=".<%=appname%>."][id$=".http_port"] {
width: 100px !important;
min-width: unset !important;
max-width: unset !important;
text-align: center !important;
}
}
</style>
<script type="text/javascript">
//<![CDATA[
@@ -128,7 +144,7 @@ local appname = api.appname
}
}
}
document.addEventListener("DOMContentLoaded", function () {
document.addEventListener("DOMContentLoaded", () => setTimeout(() => {
const hiddenSelect = document.getElementById("cbid.<%=appname%>.<%=self.global_cfgid%>.node");
let o_val = hiddenSelect.value
const o_hasItem = shunt_list.find(element => element.id == o_val);
@@ -169,7 +185,7 @@ local appname = api.appname
}
}
go();
});
}, 100));
//setTimeout("go()", 1000);
//]]>
</script>
@@ -1,7 +1,8 @@
<%
local api = require "luci.passwall2.api"
local appname = api.appname
-%>
<script src="<%=resource%>/view/<%=api.appname%>/Sortable.min.js"></script>
<script src="<%=resource%>/view/<%=appname%>/Sortable.min.js"></script>
<style>
table .cbi-button-up,
@@ -34,6 +35,34 @@ table .cbi-button-down,
background-color: rgba(131, 191, 255, 0.7) !important;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
@media screen and (max-width: 1152px) {
#cbi-<%=appname%>-haproxy_config .cbi-section-table-titles {
display: none !important;
}
}
@media screen and (min-width: 1152px) {
#cbi-<%=appname%>-haproxy_config input[id*=".<%=appname%>."][id$=".haproxy_port"],
#cbi-<%=appname%>-haproxy_config input[id*=".<%=appname%>."][id$=".lbweight"] {
width: 100px !important;
min-width: unset !important;
max-width: unset !important;
text-align: center !important;
}
#cbi-<%=appname%>-haproxy_config select[id*=".<%=appname%>."][id$=".export"] {
width: 130px !important;
min-width: unset !important;
max-width: unset !important;
}
#cbi-<%=appname%>-haproxy_config select[id*=".<%=appname%>."][id$=".backup"] {
width: 100px !important;
min-width: unset !important;
max-width: unset !important;
}
}
</style>
<script type="text/javascript">
//<![CDATA[
@@ -85,7 +114,7 @@ table .cbi-button-down,
//节点列表添加拖拽排序
document.addEventListener("DOMContentLoaded", function () {
function initSortableForTable() {
var section = document.getElementById("cbi-<%=api.appname%>-haproxy_config");
var section = document.getElementById("cbi-<%=appname%>-haproxy_config");
if (!section) return;
hideSortColumn(section);
@@ -139,8 +168,8 @@ table .cbi-button-down,
var rows = root.querySelectorAll("tr.cbi-section-table-row");
rows.forEach(function (tr) {
var id = tr.id || "";
if (id.startsWith("cbi-<%=api.appname%>-"))
id = id.replace("cbi-<%=api.appname%>-", "");
if (id.startsWith("cbi-<%=appname%>-"))
id = id.replace("cbi-<%=appname%>-", "");
order.push(id);
});
return order;
@@ -53,7 +53,7 @@
}
}
}
document.addEventListener("DOMContentLoaded", function () {
document.addEventListener("DOMContentLoaded", () => setTimeout(() => {
refresh_depends();
const table_dom = document.getElementById("cbi-passwall2-shunt_option_list");
if (table_dom) {
@@ -71,6 +71,6 @@
}
}
}
});
}, 100));
//]]>
</script>
@@ -306,7 +306,7 @@ local current_node = map:get(section)
params += opt.query("alpn", dom_prefix + "alpn");
params += opt.query("sni", dom_prefix + "tls_serverName");
params += opt.query("allowinsecure", dom_prefix + "tls_allowInsecure");
params += opt.query("pcs", dom_prefix + "tls_CertSha");
params += opt.query("pcs", dom_prefix + "tls_pinSHA256");
params += opt.query("vcn", dom_prefix + "tls_CertByName");
params += opt.query("ech", dom_prefix + "ech_config");
}
@@ -499,7 +499,7 @@ local current_node = map:get(section)
params += opt.query("alpn", dom_prefix + "alpn");
params += opt.query("sni", dom_prefix + "tls_serverName");
params += opt.query("allowinsecure", dom_prefix + "tls_allowInsecure");
params += opt.query("pcs", dom_prefix + "tls_CertSha");
params += opt.query("pcs", dom_prefix + "tls_pinSHA256");
params += opt.query("vcn", dom_prefix + "tls_CertByName");
params += opt.query("ech", dom_prefix + "ech_config");
}
@@ -570,7 +570,7 @@ local current_node = map:get(section)
params += opt.query("alpn", dom_prefix + "alpn");
params += opt.query("sni", dom_prefix + "tls_serverName");
params += opt.query("allowinsecure", dom_prefix + "tls_allowInsecure");
params += opt.query("pcs", dom_prefix + "tls_CertSha");
params += opt.query("pcs", dom_prefix + "tls_pinSHA256");
params += opt.query("vcn", dom_prefix + "tls_CertByName");
params += opt.query("ech", dom_prefix + "ech_config");
}
@@ -591,14 +591,14 @@ local current_node = map:get(section)
var params = "";
params += opt.query("sni", dom_prefix + "tls_serverName");
params += opt.query("insecure", dom_prefix + "tls_allowInsecure");
params += opt.query("pcs", dom_prefix + "tls_CertSha");
params += opt.query("pinSHA256", dom_prefix + "tls_pinSHA256");
params += opt.query("pcs", dom_prefix + "tls_pinSHA256");
params += opt.query("vcn", dom_prefix + "tls_CertByName");
var v_password = null;
if (v_type === "Hysteria2") {
v_password = opt.get("hysteria2_auth_password");
params += opt.query("pinSHA256", "hysteria2_tls_pinSHA256");
var dom_obfs = opt.get("hysteria2_obfs");
if (dom_obfs && dom_obfs.value != "") {
params += "&obfs=" + "salamander";
@@ -1069,7 +1069,7 @@ local current_node = map:get(section)
}
opt.set(dom_prefix + 'ech', !!queryParam.ech);
opt.set(dom_prefix + 'ech_config', queryParam.ech || '');
opt.set(dom_prefix + 'tls_CertSha', queryParam.pcs || '');
opt.set(dom_prefix + 'tls_pinSHA256', queryParam.pcs || '');
opt.set(dom_prefix + 'tls_CertByName', queryParam.vcn || '');
}
@@ -1308,7 +1308,7 @@ local current_node = map:get(section)
if (queryParam.security === "tls") {
opt.set(dom_prefix + 'tls_serverName', queryParam.peer || queryParam.sni || "");
opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default');
opt.set(dom_prefix + 'tls_CertSha', queryParam.pcs || '');
opt.set(dom_prefix + 'tls_pinSHA256', queryParam.pcs || '');
opt.set(dom_prefix + 'tls_CertByName', queryParam.vcn || '');
}
opt.set(
@@ -1472,7 +1472,7 @@ local current_node = map:get(section)
}
opt.set(dom_prefix + 'ech', !!queryParam.ech);
opt.set(dom_prefix + 'ech_config', queryParam.ech || '');
opt.set(dom_prefix + 'tls_CertSha', queryParam.pcs || '');
opt.set(dom_prefix + 'tls_pinSHA256', queryParam.pcs || '');
opt.set(dom_prefix + 'tls_CertByName', queryParam.vcn || '');
}
@@ -1613,9 +1613,6 @@ local current_node = map:get(section)
if (queryParam["obfs-password"] || queryParam["obfs_password"]) {
opt.set(dom_prefix + 'obfs', queryParam["obfs-password"] || queryParam["obfs_password"]);
}
if (queryParam.pinSHA256) {
opt.set(dom_prefix + 'tls_pinSHA256', queryParam.pinSHA256);
}
opt.set(dom_prefix + 'hop', queryParam.mport || "");
}
@@ -1627,7 +1624,7 @@ local current_node = map:get(section)
dom_prefix + 'tls_allowInsecure',
!((queryParam.allowinsecure ?? '0') === '0' && (queryParam.allowInsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0')
);
opt.set(dom_prefix + 'tls_CertSha', queryParam.pcs || '');
opt.set(dom_prefix + 'tls_pinSHA256', queryParam.pcs || queryParam.pinSHA256 || '');
opt.set(dom_prefix + 'tls_CertByName', queryParam.vcn || '');
if (m.hash) {
opt.set('remarks', decodeURIComponent(m.hash.substr(1)));
@@ -34,6 +34,12 @@ table .cbi-button-down,
background-color: rgba(131, 191, 255, 0.7) !important;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
@media screen and (max-width: 1152px) {
#cbi-<%=api.appname%>-subscribe_list .cbi-section-table-titles {
display: none !important;
}
}
</style>
<script type="text/javascript">
+12
View File
@@ -2230,6 +2230,12 @@ msgstr "ورودی %s:%s"
msgid "Node: %s:%s, Weight: %s"
msgstr "گره: %s:%s، وزن: %s"
msgid "[Rule update] instance is running; please try again later."
msgstr "نمونه [به‌روزرسانی قانون] در حال اجرا است؛ لطفاً بعداً دوباره امتحان کنید."
msgid "[Subscription] instance is running; [Rule Update] queue and wait."
msgstr "نمونه [اشتراک] در حال اجرا است؛ [به‌روزرسانی قانون] در صف انتظار قرار می‌گیرد."
msgid "Start updating the rules..."
msgstr "شروع به‌روزرسانی قوانین..."
@@ -2383,6 +2389,12 @@ msgstr "دریافت محتوای اشتراک برای [%s] خالی است. ا
msgid "Proxy"
msgstr "پروکسی"
msgid "[Subscription] instance is running; please try again later."
msgstr "نمونه [اشتراک] در حال اجرا است؛ لطفاً بعداً دوباره امتحان کنید."
msgid "[Rule Update] instance is running; [Subscription] queue and wait."
msgstr "نمونه [به‌روزرسانی قانون] در حال اجرا است؛ [اشتراک] در صف انتظار قرار دارد."
msgid "Start subscribing..."
msgstr "شروع اشتراک‌گیری..."
+12
View File
@@ -2228,6 +2228,12 @@ msgstr "Вход %s:%s"
msgid "Node: %s:%s, Weight: %s"
msgstr "Сервер: %s:%s, вес: %s"
msgid "[Rule update] instance is running; please try again later."
msgstr "Экземпляр [Обновление правила] запущен; пожалуйста, попробуйте позже."
msgid "[Subscription] instance is running; [Rule Update] queue and wait."
msgstr "Экземпляр [Подписка] запущен; [Обновление правила] поставить в очередь и ждать."
msgid "Start updating the rules..."
msgstr "Начало обновления правил..."
@@ -2381,6 +2387,12 @@ msgstr "Содержимое подписки [%s] пусто. Возможно,
msgid "Proxy"
msgstr "Прокси"
msgid "[Subscription] instance is running; please try again later."
msgstr "Экземпляр [Подписки] запущен; пожалуйста, попробуйте позже."
msgid "[Rule Update] instance is running; [Subscription] queue and wait."
msgstr "Экземпляр [Обновление правила] запущен; [Подписка] ставится в очередь и ожидает."
msgid "Start subscribing..."
msgstr "Начало подписки..."
+12
View File
@@ -2222,6 +2222,12 @@ msgstr "入口 %s:%s"
msgid "Node: %s:%s, Weight: %s"
msgstr "节点:%s:%s,权重:%s"
msgid "[Rule update] instance is running; please try again later."
msgstr "[规则更新]实例正在运行,请稍后再试..."
msgid "[Subscription] instance is running; [Rule Update] queue and wait."
msgstr "[订阅]实例正在运行,[规则更新]进入队列等待..."
msgid "Start updating the rules..."
msgstr "开始更新规则..."
@@ -2375,6 +2381,12 @@ msgstr "获取到的【%s】订阅内容为空,可能是订阅地址无效,
msgid "Proxy"
msgstr "代理"
msgid "[Subscription] instance is running; please try again later."
msgstr "[订阅]实例正在运行,请稍后再试..."
msgid "[Rule Update] instance is running; [Subscription] queue and wait."
msgstr "[规则更新]实例正在运行,[订阅]进入队列等待..."
msgid "Start subscribing..."
msgstr "开始订阅..."
+12
View File
@@ -2222,6 +2222,12 @@ msgstr "入口 %s:%s"
msgid "Node: %s:%s, Weight: %s"
msgstr "節點:%s:%s,權重:%s"
msgid "[Rule update] instance is running; please try again later."
msgstr "[規則更新]實例正在運行,請稍後再試..."
msgid "[Subscription] instance is running; [Rule Update] queue and wait."
msgstr "[訂閱]實例正在運行,[規則更新]進入隊列等待..."
msgid "Start updating the rules..."
msgstr "開始更新規則..."
@@ -2375,6 +2381,12 @@ msgstr "獲取到的【%s】訂閱內容為空,可能是訂閱地址無效,
msgid "Proxy"
msgstr "代理"
msgid "[Subscription] instance is running; please try again later."
msgstr "[訂閱]實例正在運行,請稍後再試..."
msgid "[Rule Update] instance is running; [Subscription] queue and wait."
msgstr "[規則更新]實例正在運行,[訂閱]進入隊列等待..."
msgid "Start subscribing..."
msgstr "開始訂閱..."
@@ -64,6 +64,15 @@ stop() {
restart() {
set_lock
[ $? == 1 ] && log_i18n 0 "The script is already running, do not run it again. Exit." && exit 0
rule_lock=${LOCK_FILE_DIR}/${CONFIG}_rule_update.lock
sub_lock=${LOCK_FILE_DIR}/${CONFIG}_subscribe.lock
for i in $(seq 1 300); do
if [ ! -f "$rule_lock" ] && [ ! -f "$sub_lock" ]; then
break
fi
sleep 2
done
rm -f "$rule_lock" "$sub_lock"
$APP_FILE stop
$APP_FILE start $1
unset_lock
@@ -8,7 +8,7 @@ listen_port=$2
server_address=$3
server_port=$4
pgrep -af "${CONFIG}/" | awk '/app\.sh.*(start|stop)/ || /nftables\.sh/ || /iptables\.sh/ { found = 1 } END { exit !found }' && {
pgrep -af "${CONFIG}/" | grep -E 'app\.sh.*(start|stop)|nftables\.sh|iptables\.sh|subscribe\.lua' >/dev/null && {
exit 0
}
@@ -649,13 +649,9 @@ add_firewall_rule() {
ipset -! create $IPSET_LAN6 nethash family inet6 maxelem 1048576
ipset -! create $IPSET_VPS6 nethash family inet6 maxelem 1048576
ipset -! create $IPSET_WAN6 nethash family inet6 maxelem 1048576
ipset -! -R <<-EOF
$(ip address show | grep -w "inet" | awk '{print $2}' | awk -F '/' '{print $1}' | sed -e "s/^/add $IPSET_LOCAL /")
EOF
ipset -! -R <<-EOF
$(ip address show | grep -w "inet6" | awk '{print $2}' | awk -F '/' '{print $1}' | sed -e "s/^/add $IPSET_LOCAL6 /")
EOF
get_local_ips ip4 | sed "s/^/add $IPSET_LOCAL /" | ipset -! -R
get_local_ips ip6 | sed "s/^/add $IPSET_LOCAL6 /" | ipset -! -R
ipset -! -R <<-EOF
$(gen_lanlist | sed -e "s/^/add $IPSET_LAN /")
@@ -749,9 +745,9 @@ add_firewall_rule() {
$ipt_n -N PSW2_DNS
if [ $(config_t_get global dns_redirect "1") = "0" ]; then
#Only hijack when dest address is local IP
$ipt_n -I PREROUTING $(dst $IPSET_LOCAL) -j PSW2_DNS
$ipt_n -I PREROUTING -m set --match-set $IPSET_LAN src $(dst $IPSET_LOCAL) -j PSW2_DNS
else
$ipt_n -I PREROUTING -j PSW2_DNS
$ipt_n -I PREROUTING -m set --match-set $IPSET_LAN src -j PSW2_DNS
fi
$ipt_m -N PSW2_DIVERT
@@ -799,9 +795,9 @@ add_firewall_rule() {
$ip6t_n -N PSW2_DNS
if [ $(config_t_get global dns_redirect "1") = "0" ]; then
#Only hijack when dest address is local IP
$ip6t_n -I PREROUTING $(dst $IPSET_LOCAL6) -j PSW2_DNS
$ip6t_n -I PREROUTING -m set --match-set $IPSET_LAN6 src $(dst $IPSET_LOCAL6) -j PSW2_DNS
else
$ip6t_n -I PREROUTING -j PSW2_DNS
$ip6t_n -I PREROUTING -m set --match-set $IPSET_LAN6 src -j PSW2_DNS
fi
$ip6t_m -N PSW2_DIVERT
@@ -712,8 +712,8 @@ add_firewall_rule() {
gen_nftset $NFTSET_VPS6 ipv6_addr 0 "-1"
gen_nftset $NFTSET_WAN6 ipv6_addr 0 "-1"
ip address show | grep -w "inet" | awk '{print $2}' | awk -F '/' '{print $1}' | insert_nftset $NFTSET_LOCAL "-1"
ip address show | grep -w "inet6" | awk '{print $2}' | awk -F '/' '{print $1}' | insert_nftset $NFTSET_LOCAL6 "-1"
get_local_ips ip4 | insert_nftset $NFTSET_LOCAL "-1"
get_local_ips ip6 | insert_nftset $NFTSET_LOCAL6 "-1"
# Ignore special IP ranges
local lan_ifname lan_ip
@@ -786,10 +786,11 @@ add_firewall_rule() {
nft "flush chain $NFTABLE_NAME PSW2_DNS"
if [ $(config_t_get global dns_redirect "1") = "0" ]; then
#Only hijack when dest address is local IP
nft "insert rule $NFTABLE_NAME dstnat ip daddr @${NFTSET_LOCAL} jump PSW2_DNS"
nft "insert rule $NFTABLE_NAME dstnat ip6 daddr @${NFTSET_LOCAL6} jump PSW2_DNS"
nft "insert rule $NFTABLE_NAME dstnat ip saddr @${NFTSET_LAN} ip daddr @${NFTSET_LOCAL} jump PSW2_DNS"
nft "insert rule $NFTABLE_NAME dstnat ip6 saddr @${NFTSET_LAN6} ip6 daddr @${NFTSET_LOCAL6} jump PSW2_DNS"
else
nft "insert rule $NFTABLE_NAME dstnat jump PSW2_DNS"
nft "insert rule $NFTABLE_NAME dstnat ip saddr @${NFTSET_LAN} jump PSW2_DNS"
nft "insert rule $NFTABLE_NAME dstnat ip6 saddr @${NFTSET_LAN6} jump PSW2_DNS"
fi
# for ipv4 ipv6 tproxy mark
@@ -184,6 +184,34 @@ if geoip_update == "0" and geosite_update == "0" then
os.exit(0)
end
local function check_instance(action)
local rule_lock = "/var/lock/" .. name .. "_rule_update.lock"
local sub_lock = "/var/lock/" .. name .. "_subscribe.lock"
if action == "start" then
math.randomseed(os.time() + math.floor(os.clock() * 1000))
api.nixio.nanosleep(0, math.random(100, 1000) * 1000000)
if fs.access(rule_lock) then
log(0, api.i18n.translatef("[Rule update] instance is running; please try again later.") .. "\n")
os.exit(0)
else
luci.sys.call("touch " .. rule_lock)
end
elseif action == "end" then
luci.sys.call("rm -f " .. rule_lock)
return
end
if fs.access(sub_lock) then
log(0, api.i18n.translatef("[Subscription] instance is running; [Rule Update] queue and wait.") .. "\n")
end
while fs.access(sub_lock) do
api.nixio.nanosleep(2, 0)
end
end
check_instance("start")
log(0, api.i18n.translate("Start updating the rules..."))
local function safe_call(func, err_msg)
xpcall(func, function(e)
@@ -221,3 +249,5 @@ if reboot == 1 then
api.uci_save(uci, name, true, true)
end
log(0, api.i18n.translate("The rules have been updated..."))
check_instance("end")
@@ -5,6 +5,12 @@ APP_FILE=${APP_PATH}/app.sh
flag=0
check_process() {
while pgrep -af "${CONFIG}/" | grep -E 'app\.sh.*(start|stop)|nftables\.sh|iptables\.sh|subscribe\.lua' >/dev/null; do
sleep 6s
done
}
test_url() {
local url=$1
local try=1
@@ -48,6 +54,7 @@ test_node() {
local node_id=$1
local _type=$(echo $(config_n_get ${node_id} type) | tr 'A-Z' 'a-z')
[ -n "${_type}" ] && {
check_process
local _tmp_port=$(get_new_port 48800 tcp,udp)
NO_REC_PROCESS=1 $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
sleep 2s
@@ -92,6 +99,7 @@ test_auto_switch() {
if [ "$restore_switch" = "1" ] && [ -n "$main_node" ] && [ "$now_node" != "$main_node" ]; then
test_node ${main_node}
[ $? -eq 0 ] && {
check_process
# The main node is working properly; switch to the main node.
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)"
$APP_FILE socks_node_switch flag=${id} new_node=${main_node}
@@ -135,6 +143,7 @@ test_auto_switch() {
# [ -z "$(echo $b_nodes | grep $main_node)" ] && uci add_list $CONFIG.${id}.autoswitch_backup_node=$main_node
# uci commit $CONFIG
# }
check_process
log_i18n 0 "Socks switch detection: %s 【%s:[%s]】 normal, switch to this node!" "${id}" "$(config_n_get $new_node type)" "$(config_n_get $new_node remarks)"
$APP_FILE socks_node_switch flag=${id} new_node=${new_node}
[ $? -eq 0 ] && {
@@ -172,10 +181,7 @@ start() {
sleep 6s
continue
}
pgrep -af "${CONFIG}/" | awk '/app\.sh.*(start|stop)/ || /nftables\.sh/ || /iptables\.sh/ { found = 1 } END { exit !found }' && {
sleep 6s
continue
}
check_process
touch $LOCK_FILE
test_auto_switch "$backup_node"
rm -f $LOCK_FILE
@@ -23,7 +23,6 @@ local uci = api.uci
local fs = api.fs
local log = api.log
local i18n = api.i18n
uci:revert(appname)
local has_ss = api.is_finded("ss-redir")
local has_ss_rust = api.is_finded("sslocal")
@@ -301,7 +300,12 @@ do
currentNodes[#currentNodes + 1] = {
log = true,
node = node,
currentNode = node and uci:get_all(appname, node) or nil,
currentNode = (function()
if node and node:find("Socks_") then
return { Socks = node }
end
return node and uci:get_all(appname, node) or nil
end)(),
remarks = node,
set = function(o, server)
if o and server and server ~= "nil" then
@@ -325,7 +329,7 @@ do
-- Backup Node
local currentNode = uci:get_all(appname, node_id) or nil
if currentNode and currentNode.fallback_node then
if currentNode and currentNode.fallback_node and not currentNode.fallback_node:find("Socks_") then
CONFIG[#CONFIG + 1] = {
log = true,
id = node_id,
@@ -349,7 +353,12 @@ do
currentNodes[#currentNodes + 1] = {
log = true,
node = node,
currentNode = node and uci:get_all(appname, node) or nil,
currentNode = (function()
if node and node:find("Socks_") then
return { Socks = node }
end
return node and uci:get_all(appname, node) or nil
end)(),
remarks = node,
set = function(o, server)
if o and server and server ~= "nil" then
@@ -645,7 +654,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
if info.tls == "tls" or info.tls == "1" then
result.tls = "1"
result.tls_serverName = (info.sni and info.sni ~= "") and info.sni or info.host
result.tls_CertSha = info.pcs
result.tls_pinSHA256 = info.pcs
result.tls_CertByName = info.vcn
local insecure = info.allowinsecure or info.allowInsecure or info.insecure
result.tls_allowInsecure = (insecure == "1" or insecure == "0") and insecure or (sub_allowinsecure and "1" or "0")
@@ -915,7 +924,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
result.ech = "1"
result.ech_config = params.ech
end
result.tls_CertSha = params.pcs
result.tls_pinSHA256 = params.pcs
result.tls_CertByName = params.vcn
if params.security == "reality" then
result.reality = "1"
@@ -1028,7 +1037,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
result.tls = '1'
result.tls_serverName = params.peer or params.sni or ""
result.tls_CertSha = params.pcs
result.tls_pinSHA256 = params.pcs
result.tls_CertByName = params.vcn
local insecure = params.allowinsecure or params.allowInsecure or params.insecure
result.tls_allowInsecure = (insecure == "1" or insecure == "0") and insecure or (sub_allowinsecure and "1" or "0")
@@ -1271,7 +1280,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
result.ech = "1"
result.ech_config = params.ech
end
result.tls_CertSha = params.pcs
result.tls_pinSHA256 = params.pcs
result.tls_CertByName = params.vcn
if params.security == "reality" then
result.reality = "1"
@@ -1382,11 +1391,10 @@ local function processData(szType, content, add_mode, group, sub_cfg)
result.address = host_port
end
result.tls_serverName = params.sni
result.tls_CertSha = params.pcs
result.tls_pinSHA256 = params.pcs or params.pinsha256
result.tls_CertByName = params.vcn
local insecure = params.allowinsecure or params.insecure
result.tls_allowInsecure = (insecure == "1" or insecure == "0") and insecure or (sub_allowinsecure and "1" or "0")
result.hysteria2_tls_pinSHA256 = params.pinSHA256
result.hysteria2_hop = params.mport
if (sub_hysteria2_type == "sing-box" and has_singbox) or (sub_hysteria2_type == "xray" and has_xray) then
@@ -1608,9 +1616,9 @@ local function processData(szType, content, add_mode, group, sub_cfg)
end
local function curl(url, file, ua, mode)
if not url or url == "" then return 404 end
if not url or url == "" then return 22, 404 end
local curl_args = {
"-skL", "-w %{http_code}", "--retry 3", "--connect-timeout 3", "-H 'Accept-Encoding: identity'"
"-fskL", "-w %{http_code}", "--retry 3", "--connect-timeout 3", "-H 'Accept-Encoding: identity'"
}
if ua and ua ~= "" and ua ~= "curl" then
ua = (ua == "passwall2") and ("passwall2/" .. api.get_version()) or ua
@@ -1625,7 +1633,7 @@ local function curl(url, file, ua, mode)
else
return_code, result = api.curl_auto(url, file, curl_args)
end
return tonumber(result)
return return_code, tonumber(result)
end
function get_headers()
@@ -1733,6 +1741,10 @@ local function select_node(nodes, config, parentConfig)
end
if config.currentNode then
local server
-- Load balancing, keeping the original ID of the Socks [port] node in urltest.
if config.currentNode["Socks"] then
server = config.currentNode.Socks
end
-- Special priority: cfgid
if config.currentNode[".name"] then
for index, node in pairs(nodes) do
@@ -1945,7 +1957,6 @@ local function update_node(manual)
end
end
end
api.uci_save(uci, appname, true)
if next(CONFIG) then
local nodes = {}
@@ -1969,10 +1980,10 @@ local function update_node(manual)
select_node(nodes, config)
end
end
api.uci_save(uci, appname, true)
end
api.uci_save(uci, appname, true)
if arg[3] == "cron" then
if not fs.access("/var/lock/" .. appname .. ".lock") then
luci.sys.call("touch /tmp/lock/" .. appname .. "_cron.lock")
@@ -2111,8 +2122,9 @@ local execute = function()
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(1, i18n.translatef("Start subscribing: %s", '【' .. remark .. '】' .. url .. ' [' .. result .. ']'))
tmp_file = "/tmp/" .. cfgid
value.http_code = curl(url, tmp_file, ua, access_mode)
if value.http_code ~= 200 then
local return_code
return_code, value.http_code = curl(url, tmp_file, ua, access_mode)
if return_code ~= 0 then
fail_list[#fail_list + 1] = value
luci.sys.call("rm -f " .. tmp_file)
end
@@ -2151,7 +2163,36 @@ local execute = function()
end
end
local function check_instance(action)
local sub_lock = "/var/lock/" .. appname .. "_subscribe.lock"
local rule_lock = "/var/lock/" .. appname .. "_rule_update.lock"
if action == "start" then
math.randomseed(os.time() + math.floor(os.clock() * 1000))
api.nixio.nanosleep(0, math.random(100, 1000) * 1000000)
if fs.access(sub_lock) then
log(0, i18n.translatef("[Subscription] instance is running; please try again later.") .. "\n")
os.exit(0)
else
luci.sys.call("touch " .. sub_lock)
uci:revert(appname)
end
elseif action == "end" then
luci.sys.call("rm -f " .. sub_lock)
return
end
if fs.access(rule_lock) then
log(0, i18n.translatef("[Rule Update] instance is running; [Subscription] queue and wait.") .. "\n")
end
while fs.access(rule_lock) do
api.nixio.nanosleep(2, 0)
end
end
if arg[1] then
check_instance("start")
if arg[1] == "start" then
log(0, i18n.translatef("Start subscribing..."))
xpcall(execute, function(e)
@@ -2170,4 +2211,6 @@ if arg[1] then
elseif arg[1] == "truncate" then
truncate_nodes(arg[2])
end
check_instance("end")
end
@@ -484,3 +484,33 @@ get_wan_ips() {
done
echo "$NET_ADDR"
}
get_local_ips() {
local family="$1"
local ALL_IPS WAN_IPS ip NET_ADDR
if [ "$family" = "ip6" ]; then
ALL_IPS=$(ip -o -6 addr show scope global | awk '{print $4}' | cut -d/ -f1)
WAN_IPS=$(get_wan_ips ip6)
else
ALL_IPS=$(ip -o -4 addr show scope global | awk '{print $4}' | cut -d/ -f1)
WAN_IPS=$(get_wan_ips ip4)
fi
# Supplementary loop (not included in scope global)
[ "$family" = "ip6" ] && ALL_IPS="$ALL_IPS ::1"
[ "$family" != "ip6" ] && ALL_IPS="$ALL_IPS 127.0.0.1"
for ip in $ALL_IPS; do
case "$ip" in
""|0.0.0.0|::) continue ;;
esac
case " $WAN_IPS " in
*" $ip "*) continue ;;
esac
case " $NET_ADDR " in
*" $ip "*) ;;
*) NET_ADDR="${NET_ADDR:+$NET_ADDR }$ip" ;;
esac
done
for ip in $NET_ADDR; do
echo "$ip"
done
}
+1 -1
View File
@@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=mosdns
PKG_VERSION:=5.3.4
PKG_RELEASE:=7
PKG_RELEASE:=8
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/IrineSistiana/mosdns/tar.gz/v$(PKG_VERSION)?
@@ -0,0 +1,427 @@
From 64614017e674960638bac87b4a2f9c0aea0065e6 Mon Sep 17 00:00:00 2001
From: sbwml <admin@cooluc.com>
Date: Fri, 1 May 2026 01:35:59 +0800
Subject: [PATCH] add adblock_set database plugin
Signed-off-by: sbwml <admin@cooluc.com>
---
.../data_provider/adblock_set/adblock_set.go | 122 ++++++++++++++++++
.../adblock_set/adblock_set_test.go | 107 +++++++++++++++
plugin/data_provider/adblock_set/matcher.go | 43 ++++++
plugin/data_provider/adblock_set/parser.go | 115 +++++++++++++++++
plugin/enabled_plugins.go | 1 +
5 files changed, 388 insertions(+)
create mode 100644 plugin/data_provider/adblock_set/adblock_set.go
create mode 100644 plugin/data_provider/adblock_set/adblock_set_test.go
create mode 100644 plugin/data_provider/adblock_set/matcher.go
create mode 100644 plugin/data_provider/adblock_set/parser.go
--- /dev/null
+++ b/plugin/data_provider/adblock_set/adblock_set.go
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2025, IrineSistiana
+ *
+ * This file is part of mosdns.
+ *
+ * mosdns is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * mosdns is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package adblock_set
+
+import (
+ "fmt"
+ "os"
+ "sync/atomic"
+ "time"
+
+ "github.com/IrineSistiana/mosdns/v5/coremain"
+ "github.com/IrineSistiana/mosdns/v5/pkg/matcher/domain"
+ "github.com/IrineSistiana/mosdns/v5/pkg/utils"
+ "github.com/IrineSistiana/mosdns/v5/plugin/data_provider"
+ "go.uber.org/zap"
+)
+
+const PluginType = "adblock_set"
+
+func init() {
+ coremain.RegNewPluginFunc(PluginType, Init, func() any { return new(Args) })
+}
+
+func Init(bp *coremain.BP, args any) (any, error) {
+ m, err := NewAdblockSet(bp, args.(*Args))
+ if err != nil {
+ return nil, err
+ }
+ return m, nil
+}
+
+type Args struct {
+ Files []string `yaml:"files"`
+}
+
+var _ data_provider.DomainMatcherProvider = (*AdblockSet)(nil)
+
+type AdblockSet struct {
+ matcher atomic.Pointer[AdblockMatcher]
+ fw *utils.FileWatcher
+}
+
+func (d *AdblockSet) GetDomainMatcher() domain.Matcher[struct{}] {
+ return d
+}
+
+func (d *AdblockSet) Match(s string) (struct{}, bool) {
+ if m := d.matcher.Load(); m != nil {
+ return m.Match(s)
+ }
+ return struct{}{}, false
+}
+
+func (d *AdblockSet) Close() error {
+ if d.fw != nil {
+ return d.fw.Close()
+ }
+ return nil
+}
+
+func NewAdblockSet(bp *coremain.BP, args *Args) (*AdblockSet, error) {
+ ds := &AdblockSet{}
+
+ loadInner := func() (*AdblockMatcher, error) {
+ m := &AdblockMatcher{
+ blacklist: domain.NewMixMatcher[struct{}](),
+ whitelist: domain.NewMixMatcher[struct{}](),
+ }
+ for _, f := range args.Files {
+ if len(f) == 0 {
+ continue
+ }
+ file, err := os.Open(f)
+ if err != nil {
+ return nil, fmt.Errorf("failed to open file %s: %w", f, err)
+ }
+ if err := ParseRules(file, m.blacklist, m.whitelist); err != nil {
+ file.Close()
+ return nil, fmt.Errorf("failed to parse rules from %s: %w", f, err)
+ }
+ file.Close()
+ }
+ return m, nil
+ }
+
+ m, err := loadInner()
+ if err != nil {
+ return nil, err
+ }
+ ds.matcher.Store(m)
+
+ if len(args.Files) > 0 {
+ ds.fw = utils.StartFileWatcher(args.Files, time.Second*3, func(changedFiles []string) {
+ newM, err := loadInner()
+ if err == nil {
+ ds.matcher.Store(newM)
+ bp.L().Info("reloaded adblock files", zap.Strings("files", changedFiles))
+ } else {
+ bp.L().Error("failed to reload adblock files", zap.Error(err))
+ }
+ })
+ }
+
+ return ds, nil
+}
--- /dev/null
+++ b/plugin/data_provider/adblock_set/adblock_set_test.go
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2025, IrineSistiana
+ *
+ * This file is part of mosdns.
+ *
+ * mosdns is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * mosdns is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package adblock_set
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/IrineSistiana/mosdns/v5/pkg/matcher/domain"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestParseAndMatch(t *testing.T) {
+ rules := `
+! Comment
+# Another comment
+||example.com^
+@@||whitelist.com^
+|exact.com|
+@@|white-exact.com|
+/.*regex.*/
+@@/.*white-regex.*/
+*keyword*
+@@*white-keyword*
+blocked.com
+domain:ntp.org
+full:metrics.icloud.com
+`
+ blacklist := domain.NewMixMatcher[struct{}]()
+ whitelist := domain.NewMixMatcher[struct{}]()
+
+ err := ParseRules(strings.NewReader(rules), blacklist, whitelist)
+ assert.NoError(t, err)
+
+ matcher := &AdblockMatcher{
+ blacklist: blacklist,
+ whitelist: whitelist,
+ }
+
+ tests := []struct {
+ domain string
+ want bool
+ }{
+ {"example.com", true},
+ {"sub.example.com", true},
+ {"whitelist.com", false},
+ {"sub.whitelist.com", false},
+ {"exact.com", true},
+ {"sub.exact.com", false}, // |exact.com| is exact match
+ {"white-exact.com", false},
+ {"myregex123", true},
+ {"mywhite-regex123", false},
+ {"somekeywordhere", true},
+ {"some-white-keyword-here", false},
+ {"blocked.com", true},
+ {"other.com", false},
+ {"ntp.org", true}, // matched by domain:ntp.org
+ {"sub.ntp.org", true}, // matched by domain:ntp.org
+ {"metrics.icloud.com", true}, // matched by full:metrics.icloud.com
+ {"sub.metrics.icloud.com", false}, // full match only
+ }
+
+ for _, tt := range tests {
+ _, ok := matcher.Match(tt.domain)
+ assert.Equal(t, tt.want, ok, "Match(%s)", tt.domain)
+ }
+}
+
+func TestStripOptions(t *testing.T) {
+ rules := `
+||example.com^$important,third-party
+@@||whitelist.com^$dnstype=A
+`
+ blacklist := domain.NewMixMatcher[struct{}]()
+ whitelist := domain.NewMixMatcher[struct{}]()
+
+ err := ParseRules(strings.NewReader(rules), blacklist, whitelist)
+ assert.NoError(t, err)
+
+ matcher := &AdblockMatcher{
+ blacklist: blacklist,
+ whitelist: whitelist,
+ }
+
+ _, ok := matcher.Match("example.com")
+ assert.True(t, ok)
+
+ _, ok = matcher.Match("whitelist.com")
+ assert.False(t, ok)
+}
--- /dev/null
+++ b/plugin/data_provider/adblock_set/matcher.go
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2025, IrineSistiana
+ *
+ * This file is part of mosdns.
+ *
+ * mosdns is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * mosdns is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package adblock_set
+
+import (
+ "github.com/IrineSistiana/mosdns/v5/pkg/matcher/domain"
+)
+
+type AdblockMatcher struct {
+ blacklist *domain.MixMatcher[struct{}]
+ whitelist *domain.MixMatcher[struct{}]
+}
+
+func (m *AdblockMatcher) Match(s string) (struct{}, bool) {
+ // 1. Check whitelist. If matched, it's NOT a hit for the adblock set.
+ if _, ok := m.whitelist.Match(s); ok {
+ return struct{}{}, false
+ }
+
+ // 2. Check blacklist.
+ if _, ok := m.blacklist.Match(s); ok {
+ return struct{}{}, true
+ }
+
+ return struct{}{}, false
+}
--- /dev/null
+++ b/plugin/data_provider/adblock_set/parser.go
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2025, IrineSistiana
+ *
+ * This file is part of mosdns.
+ *
+ * mosdns is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * mosdns is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package adblock_set
+
+import (
+ "bufio"
+ "io"
+ "strings"
+
+ "github.com/IrineSistiana/mosdns/v5/pkg/matcher/domain"
+)
+
+// ParseRules reads AdBlock rules from r and adds them to blacklist and whitelist matchers.
+func ParseRules(r io.Reader, blacklist, whitelist *domain.MixMatcher[struct{}]) error {
+ scanner := bufio.NewScanner(r)
+ for scanner.Scan() {
+ line := strings.TrimSpace(scanner.Text())
+ if line == "" || line[0] == '!' || line[0] == '#' {
+ continue
+ }
+
+ // Handle whitelists
+ isWhitelist := false
+ if strings.HasPrefix(line, "@@") {
+ isWhitelist = true
+ line = line[2:]
+ }
+
+ // Handle comments in rules (e.g., rule # comment)
+ // But in AdBlock, # is usually for element hiding (##) or CSS selectors.
+ // For DNS rules, we ignore anything starting with #.
+ if strings.Contains(line, "##") || strings.Contains(line, "#?#") || strings.Contains(line, "#$#") {
+ continue
+ }
+
+ // Strip options (e.g., $important, $third-party)
+ if idx := strings.LastIndex(line, "$"); idx != -1 {
+ // Check if it's a regex or something else.
+ // In ABP, $ marks the start of options.
+ line = line[:idx]
+ }
+
+ if line == "" {
+ continue
+ }
+
+ target := blacklist
+ if isWhitelist {
+ target = whitelist
+ }
+
+ if err := parseAndAddRule(line, target); err != nil {
+ // Skip invalid rules but maybe we should log them?
+ continue
+ }
+ }
+ return scanner.Err()
+}
+
+func parseAndAddRule(rule string, m *domain.MixMatcher[struct{}]) error {
+ // 0. Native MosDNS rules: type:pattern
+ if strings.Contains(rule, ":") {
+ typ, _, _ := strings.Cut(rule, ":")
+ switch typ {
+ case "full", "domain", "regexp", "keyword":
+ return m.Add(rule, struct{}{})
+ }
+ }
+
+ // 1. Regex: /regexp/
+ if strings.HasPrefix(rule, "/") && strings.HasSuffix(rule, "/") && len(rule) > 2 {
+ return m.Add("regexp:"+rule[1:len(rule)-1], struct{}{})
+ }
+
+ // 2. Subdomain: ||example.com^
+ if strings.HasPrefix(rule, "||") {
+ domainStr := rule[2:]
+ if strings.HasSuffix(domainStr, "^") {
+ domainStr = domainStr[:len(domainStr)-1]
+ }
+ return m.Add("domain:"+domainStr, struct{}{})
+ }
+
+ // 3. Exact match: |example.com|
+ if strings.HasPrefix(rule, "|") && strings.HasSuffix(rule, "|") && len(rule) > 2 {
+ return m.Add("full:"+rule[1:len(rule)-1], struct{}{})
+ }
+
+ // 4. Keyword: *keyword*
+ if strings.HasPrefix(rule, "*") && strings.HasSuffix(rule, "*") && len(rule) > 2 {
+ return m.Add("keyword:"+rule[1:len(rule)-1], struct{}{})
+ }
+
+ // 5. Default: treat as exact match if it looks like a domain
+ // Many lists are just a list of domains.
+ // AdGuard Home treats rules without || or ^ as exact matches.
+ return m.Add("full:"+rule, struct{}{})
+}
--- a/plugin/enabled_plugins.go
+++ b/plugin/enabled_plugins.go
@@ -22,6 +22,7 @@ package plugin
// data providers
import (
// data provider
+ _ "github.com/IrineSistiana/mosdns/v5/plugin/data_provider/adblock_set"
_ "github.com/IrineSistiana/mosdns/v5/plugin/data_provider/domain_set"
_ "github.com/IrineSistiana/mosdns/v5/plugin/data_provider/ip_set"