mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-26 21:12:51 +08:00
🐶 Sync 2026-05-01 20:23:08
This commit is contained in:
1 parent
c29ff1458e
commit
55924a6165
44 files changed
+2280
-1514
No files matched your search
+2
-2
@@ -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)?
|
||||
|
||||
@@ -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
@@ -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'
|
||||
|
||||
@@ -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) };
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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" })
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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 "شروع اشتراکگیری..."
|
||||
|
||||
|
||||
@@ -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 "Начало подписки..."
|
||||
|
||||
|
||||
@@ -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 "开始订阅..."
|
||||
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user