diff --git a/luci-app-homeproxy-pro/Makefile b/luci-app-homeproxy-pro/Makefile
index d79190764..f0121096b 100644
--- a/luci-app-homeproxy-pro/Makefile
+++ b/luci-app-homeproxy-pro/Makefile
@@ -23,7 +23,7 @@ LUCI_DEPENDS:= \
PKG_NAME:=luci-app-homeproxy-pro
PKG_VERSION:=28.9.1.14
-PKG_RELEASE:=11
+PKG_RELEASE:=12
# Only files the package actually ships belong here. The previous list named
# four paths that exist nowhere in the tree - certs/ and ruleset/ are created at
diff --git a/luci-app-homeproxy-pro/README.md b/luci-app-homeproxy-pro/README.md
index 7c64b2ee3..3ef0fe24e 100644
--- a/luci-app-homeproxy-pro/README.md
+++ b/luci-app-homeproxy-pro/README.md
@@ -79,6 +79,9 @@
- **试验性**:不承诺 API/配置稳定,重大变更可能在 minor 版本里发生。
- **真机实测单平台**:本仓库只在 x86-64 软路由上做过端到端验证。
- 本包不会编译 sing-box。系统固件必须自带 sing-box 1.14+;否则依赖解析直接失败。
+- **升级后留意 `/etc` 下的 `.apk-new`**:apk 会保护被本地改动过的包文件——新版本写成同名的 `.apk-new` 而不覆盖它。本项目实测到 `/etc/init.d/homeproxy-pro` 如此,结果是 reload 去抖在升级后静默不生效,直到把两者合并。升级后跑一次 `find /etc -name '*.apk-new'` 确认。
+- **关闭 `ipv6_support` 会阻断 IPv6 转发**:此时 nft 会拒绝所有 IPv6 转发(包括 LAN 内部),DNS 侧同时把 AAAA 应答改为 REFUSED。这是“本路由不使用 IPv6”的完整语义——只靠 DNS 拒绝挡不住自带解析器或已缓存的地址;如果仍需要 IPv6 直连,请把该开关打开。
+- **`gfwlist` 模式依赖 dnsmasq 的地址集合**:该模式用 dnsmasq 的 `nftset=` 把解析结果写进 `homeproxy_gfw_list_v4/v6`,nft 再依据该集合决定哪些目的地进入代理(集合外直连)。如果 LAN 客户端用自带 DoH/DoT 绕过了路由器 DNS,集合不会被填充,效果是**全部直连**。所以该模式要求客户端使用路由器下发的 DNS。
## 贡献
diff --git a/luci-app-homeproxy-pro/htdocs/luci-static/resources/homeproxy-pro.js b/luci-app-homeproxy-pro/htdocs/luci-static/resources/homeproxy-pro.js
index 8949aedb8..09fcb018d 100644
--- a/luci-app-homeproxy-pro/htdocs/luci-static/resources/homeproxy-pro.js
+++ b/luci-app-homeproxy-pro/htdocs/luci-static/resources/homeproxy-pro.js
@@ -1128,6 +1128,35 @@ return baseclass.extend({
return true;
},
+ /* A regular expression that does not compile reaches sing-box as-is and
+ * takes the whole configuration with it, so the reload keeps the previous
+ * one and the user only sees "my setting did not take". Go's RE2 and this
+ * engine differ in the corners, but every typo is caught here. */
+ validateRegex(section_id, value) {
+ if (section_id && value) {
+ try {
+ new RegExp(value);
+ } catch (e) {
+ return _('Expecting: %s').format(_('valid regular expression'));
+ }
+ }
+
+ return true;
+ },
+
+ /* sing-box resolves a DNS query type name through miekg/dns's uppercase
+ * type table (option/types.go), so "aaaa" is an "unknown DNS query type"
+ * and takes the configuration down with it. A numeric code up to 65535 is
+ * valid as-is. */
+ validateQueryType(section_id, value) {
+ if (section_id && value) {
+ if (!((/^[0-9]{1,5}$/.test(value) && parseInt(value, 10) <= 65535) || /^[A-Z][A-Z0-9-]*$/.test(value)))
+ return _('Expecting: %s').format(_('a numeric code or an uppercase type name (for example AAAA)'));
+ }
+
+ return true;
+ },
+
validateUniqueValue(uciconfig, ucisection, ucioption, section_id, value) {
if (section_id) {
if (!value)
diff --git a/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client.js b/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client.js
index be5e03ae9..e052eadf5 100644
--- a/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client.js
+++ b/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client.js
@@ -160,12 +160,12 @@ return view.extend({
/* Tab ordering follows the order of these calls: routing,
* routing_node, routing_rule, dns, dns_server, dns_rule, ruleset,
- * dns_cache, control, udp_nat, tun_dns. The DNS tab sits before
- * Access Control because the pages a preset-routing-mode user
- * actually sees are Routing Settings, DNS Settings, Access Control,
- * UDP NAT Settings and TUN DNS, in that order. The shared
- * rule-section bodies live in common.js and are called out-of-order
- * with respect to their file to keep the rendering sequence intact. */
+ * control, udp_nat, tun_dns. The DNS tab sits before Access Control
+ * because the pages a preset-routing-mode user actually sees are
+ * Routing Settings, DNS Settings, Access Control, UDP NAT Settings
+ * and TUN DNS, in that order. The shared rule-section bodies live in
+ * common.js and are called out-of-order with respect to their file to
+ * keep the rendering sequence intact. */
routing.render(ctx);
nodes.renderRoutingNodes(ctx);
routing.renderRoutingRules(ctx);
@@ -173,7 +173,6 @@ return view.extend({
nodes.renderDnsServers(ctx);
dns.renderDnsRules(ctx);
subscription.render(ctx);
- dns.renderDnsCache(ctx);
access.render(ctx);
udp_nat.renderUdpNat(ctx);
tun_dns.renderTunDns(ctx);
diff --git a/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client/access.js b/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client/access.js
index ea2a2f09d..65ec18c43 100644
--- a/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client/access.js
+++ b/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client/access.js
@@ -44,8 +44,9 @@ function render(ctx) {
/* LAN IP policy start */
ss.tab('lan_ip_policy', _('LAN IP Policy'));
- so = ss.taboption('lan_ip_policy', form.ListValue, 'lan_proxy_mode', _('Proxy filter mode'));
- so.value('disabled', _('Disable'));
+ so = ss.taboption('lan_ip_policy', form.ListValue, 'lan_proxy_mode', _('Proxy filter mode'),
+ _('"No filter" proxies every LAN client, which is the default; the other two values select clients by the lists below.'));
+ so.value('disabled', _('No filter (proxy every client)'));
so.value('listed_only', _('Proxy listed only'));
so.value('except_listed', _('Proxy all except listed'));
so.default = 'disabled';
diff --git a/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client/common.js b/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client/common.js
index e57ff0666..b45a84917 100644
--- a/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client/common.js
+++ b/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client/common.js
@@ -113,6 +113,7 @@ function renderRuleSection(s, kind, self) {
if (is_dns) {
so = ss.taboption('field_other', form.DynamicList, 'query_type', _('Query type'),
_('Match query type.'));
+ so.validate = hp.validateQueryType;
so.modalonly = true;
so = addNetwork();
@@ -145,7 +146,7 @@ function renderRuleSection(s, kind, self) {
delete this.vallist;
uci.sections('homeproxy-pro', 'ruleset', (res) => {
- if (res.enabled === '1')
+ if (res.enabled !== '0')
this.value(res['.name'], res.label);
});
@@ -167,12 +168,15 @@ function renderRuleSection(s, kind, self) {
* (sniff/hijack are emitted by the proxy-mode builder,
* not exposed in the form).
*
- * `route-options` is a routing_rule-only action: sing-box 1.14's dns_rule
- * schema has no such action, and emitting it would make the whole dns block
- * fail with "unknown action". Earlier code added it to both branches and
- * the depends() guards for dns_disable_cache / rewrite_ttl / client_subnet
- * pinned a stale 'route-options' in the UCI that resurfaced as "unknown
- * field" the next time the user flipped back to 'route'. */
+ * `route-options` is offered for routing rules only. sing-box 1.14's DNS
+ * rule action list does contain a `route-options`, but it takes a
+ * different field set (cache / TTL / timeout / client-subnet, and no
+ * outbound or override), which this shared form does not model - offering
+ * it here would emit routing fields the DNS schema refuses. Earlier code
+ * added it to both branches and the depends() guards for dns_disable_cache
+ * / rewrite_ttl / client_subnet pinned a stale 'route-options' in UCI that
+ * resurfaced as "unknown field" the next time the user flipped back to
+ * 'route'. */
so = ss.taboption('field_other', form.ListValue, 'action', _('Action'));
so.value('route', _('Route'));
so.value('reject', _('Reject'));
@@ -197,7 +201,7 @@ function renderRuleSection(s, kind, self) {
this.value('default-dns', _('Default DNS (issued by WAN)'));
this.value('system-dns', _('System DNS'));
uci.sections('homeproxy-pro', 'dns_server', (res) => {
- if (res.enabled === '1')
+ if (res.enabled !== '0')
this.value(res['.name'], res.label);
});
@@ -334,7 +338,7 @@ function renderRuleSection(s, kind, self) {
this.value('direct-out', _('Direct'));
uci.sections('homeproxy-pro', 'routing_node', (res) => {
- if (res.enabled === '1')
+ if (res.enabled !== '0')
this.value(res['.name'], res.label);
});
@@ -348,39 +352,48 @@ function renderRuleSection(s, kind, self) {
_('Override the connection destination address.'));
so.datatype = 'ipaddr';
so.depends('action', 'route');
+ so.depends('action', 'route-options');
so.modalonly = true;
so = ss.taboption('field_other', form.Value, 'override_port', _('Override port'),
_('Override the connection destination port.'));
so.datatype = 'port';
so.depends('action', 'route');
+ so.depends('action', 'route-options');
so.modalonly = true;
so = ss.taboption('field_other', form.Flag, 'udp_disable_domain_unmapping', _('Disable UDP domain unmapping'),
_('If enabled, for UDP proxy requests addressed to a domain, the original packet address will be sent in the response instead of the mapped domain.'));
so.depends('action', 'route');
+ so.depends('action', 'route-options');
so.modalonly = true;
so = ss.taboption('field_other', form.Flag, 'udp_connect', _('connect UDP connections'),
_('If enabled, attempts to connect UDP connection to the destination instead of listen.'));
so.depends('action', 'route');
+ so.depends('action', 'route-options');
so.modalonly = true;
so = ss.taboption('field_other', form.Value, 'udp_timeout', _('UDP timeout'),
_('Timeout for UDP connections.
Setting a larger value than the UDP timeout in inbounds will have no effect.'));
so.datatype = 'uinteger';
so.depends('action', 'route');
+ so.depends('action', 'route-options');
so.modalonly = true;
so = ss.taboption('field_other', form.Flag, 'tls_record_fragment', _('TLS record fragment'),
_('Fragment TLS handshake into multiple TLS records.'));
so.depends('action', 'route');
+ so.depends('action', 'route-options');
so.modalonly = true;
+ so.depends({'tls_fragment': '1', '!reverse': true});
so = ss.taboption('field_other', form.Flag, 'tls_fragment', _('TLS fragment'),
_('Fragment TLS handshakes. Due to poor performance, try %s first.').format(
_('TLS record fragment')));
so.depends('action', 'route');
+ so.depends('action', 'route-options');
+ so.depends({'tls_record_fragment': '1', '!reverse': true});
so.modalonly = true;
so = ss.taboption('field_other', form.Value, 'tls_fragment_fallback_delay', _('Fragment fallback delay'),
@@ -394,6 +407,7 @@ function renderRuleSection(s, kind, self) {
_('Inject a forged TLS ClientHello carrying this SNI before the real one to fool SNI-filtering middleboxes. Requires elevated privileges.'));
so.datatype = 'hostname';
so.depends('action', 'route');
+ so.depends('action', 'route-options');
so.modalonly = true;
so = ss.taboption('field_other', form.ListValue, 'tls_spoof_method', _('TLS spoof method (1.14)'),
@@ -404,6 +418,7 @@ function renderRuleSection(s, kind, self) {
so.value('wrong-md5', _('wrong-md5'));
so.value('wrong-timestamp', _('wrong-timestamp'));
so.depends('action', 'route');
+ so.depends('action', 'route-options');
so.depends('tls_spoof', /[\s\S]/);
so.modalonly = true;
@@ -417,7 +432,7 @@ function renderRuleSection(s, kind, self) {
this.value('default-dns', _('Default DNS (issued by WAN)'));
this.value('system-dns', _('System DNS'));
uci.sections('homeproxy-pro', 'dns_server', (res) => {
- if (res.enabled === '1')
+ if (res.enabled !== '0')
this.value(res['.name'], res.label);
});
@@ -490,6 +505,7 @@ function renderRuleSection(s, kind, self) {
so = ss.taboption('field_host', form.DynamicList, 'domain_regex', _('Domain regex'),
_('Match domain using regular expression.'));
+ so.validate = hp.validateRegex;
so.modalonly = true;
so = ss.taboption('field_host', form.DynamicList, 'source_ip_cidr', _('Source IP CIDR'),
@@ -560,6 +576,7 @@ function renderRuleSection(s, kind, self) {
so = ss.taboption('fields_process', form.DynamicList, 'process_path_regex', _('Process path (regex)'),
_('Match process path using regular expression.'));
+ so.validate = hp.validateRegex;
so.modalonly = true;
}
diff --git a/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js b/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js
index 993a0e820..7d2ef492c 100644
--- a/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js
+++ b/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js
@@ -13,17 +13,20 @@
'require homeproxy-pro as hp';
'require view.homeproxy-pro.client.common as common';
-/* The four cache options exist on the 'dns' NamedSection in both the custom
- * mode tab (renderDnsSettings) and the preset mode tab (renderDnsCache).
- * Both used to spell them out separately, which registered the same four
- * option names twice in one map: LuCI keeps the first registration for writes
- * and lookupOption(), so the second copy's dependencies did nothing while its
- * description could drift. They are declared here once and rendered from
- * wherever the tab needs them, in the original order.
+/* DNS settings: both routing-mode families in one tab.
*
- * The depends() are the custom-mode copy's: with the cache disabled the
- * optimistic cache and its persisted file have no effect, so both are hidden
- * (disable_cache_expire is itself conditional on disable_cache). */
+ * They used to be two tabs with the same title ('dns' and 'dns_cache'), each
+ * wrapping the same 'dns' NamedSection. Two form.NamedSection instances over
+ * one UCI section produce identical cbids, and LuCI resolves a cbid by taking
+ * the first matching element in DOM order - which follows tab registration,
+ * i.e. the custom-mode tab. The four cache options were registered in both,
+ * so under a preset routing mode the controls the user could actually see were
+ * reading and writing an element inside a hidden tab: "Store DNS cache" and
+ * "DNS query timeout" did not take.
+ *
+ * The tab is declared once now and no option name is registered twice, so
+ * there is no first-match to get wrong. Each option carries the routing mode
+ * it belongs to as a depends(). */
function renderDnsCacheOptions(ss) {
let so;
@@ -50,85 +53,23 @@ function renderDnsCacheOptions(ss) {
* routing.renderRoutingRules() so the NamedSection chain reads
* config -> routing_node -> routing_rule -> dns from left to right. */
function renderDnsSettings(ctx) {
- const { s } = ctx;
+ const { s, stubValidator } = ctx;
let o, ss, so;
s.tab('dns', _('DNS Settings'));
- o = s.taboption('dns', form.SectionValue, '_dns', form.NamedSection, 'dns', 'homeproxy-pro');
- o.depends('routing_mode', 'custom');
- ss = o.subsection;
- so = ss.option(form.ListValue, 'default_strategy', _('Default DNS strategy'),
- _('The DNS strategy for resolving the domain name in the address.'));
- for (let i in hp.dns_strategy)
- so.value(i, hp.dns_strategy[i]);
-
- so = ss.option(form.ListValue, 'default_server', _('Default DNS server'));
- so.load = function(section_id) {
- delete this.keylist;
- delete this.vallist;
-
- this.value('default-dns', _('Default DNS (issued by WAN)'));
- this.value('system-dns', _('System DNS'));
- uci.sections('homeproxy-pro', 'dns_server', (res) => {
- if (res.enabled === '1')
- this.value(res['.name'], res.label);
- });
-
- return this.super('load', section_id);
- }
- so.default = 'default-dns';
- so.rmempty = false;
-
- so = ss.option(form.Flag, 'disable_cache', _('Disable DNS cache'));
-
- so = ss.option(form.Flag, 'disable_cache_expire', _('Disable cache expire'));
- so.depends('disable_cache', '0');
-
- so = ss.option(form.Value, 'client_subnet', _('EDNS Client subnet'),
- _('Append a edns0-subnet OPT extra record with the specified IP prefix to every query by default.
' +
- 'If value is an IP address instead of prefix, /32 or /128 will be appended automatically.'));
- so.datatype = 'or(cidr, ipaddr)';
-
- renderDnsCacheOptions(ss);
- /* DNS settings end */
-}
-
-/* DNS rules sub-section. Split out from renderDnsSettings() so the
- * orchestrator can call it after nodes.renderDnsServers() and preserve
- * the original tab ordering (dns, dns_server, dns_rule). */
-function renderDnsRules(ctx) {
- const { s, self } = ctx;
-
- /* DNS rules start */
- common.renderRuleSection(s, 'dns', self);
- /* DNS rules end */
-}
-
-/* DNS tab for the preset routing modes. Titled 'DNS Settings' exactly like
- * the custom-mode tab in renderDnsSettings(): the two are mutually exclusive
- * (custom vs the presets), so whichever one a user lands on is called the
- * same thing.
- *
- * The three server options below are config.* (not dns.*) options that used
- * to live on the routing tab. They are DNS settings, and the routing page
- * only carried them because of the order the old monolithic client.js
- * happened to be written in. They stay on the 'config' NamedSection, so
- * only the tab they render under changed. */
-function renderDnsCache(ctx) {
- const { s, stubValidator } = ctx;
- let o, ss;
-
- s.tab('dns_cache', _('DNS Settings'));
-
- /* The name says overseas because that is the only thing that belongs
+ /* The preset-mode fields. They belong to the 'config' section, not to
+ * 'dns', so they were never part of the duplicate-cbid problem above -
+ * they are rendered here because this is the only DNS tab now.
+ *
+ * The name says overseas because that is the only thing that belongs
* here: every query this field answers goes to a domain that is not on
* the China path, and a domestic resolver answers those with polluted or
* NXDOMAIN results. The Chinese public resolvers that used to be listed
* below a divider were therefore a footgun with a helpful-looking label -
* the one combination that reliably breaks a working proxy. Only the
* overseas public resolvers are offered now. */
- o = s.taboption('dns_cache', form.Value, 'dns_server', _('Overseas DNS server'),
+ o = s.taboption('dns', form.Value, 'dns_server', _('Overseas DNS server'),
_('Resolves every domain that is not on the China DNS path, through the selected proxy node. ' +
'A domestic resolver answers these queries with polluted or NXDOMAIN results, which is what ' +
'makes a foreign site fail to resolve while the proxy itself works - so only overseas services ' +
@@ -164,11 +105,13 @@ function renderDnsCache(ctx) {
return true;
}
- o = s.taboption('dns_cache', form.Value, 'china_dns_server', _('China DNS server'),
+ o = s.taboption('dns', form.Value, 'china_dns_server', _('China DNS server'),
_('Resolves the domains on the China path, so it has to be a domestic service to get the ' +
'correct local CDN answers. Keep the default; the upstream may block UDP/53, in which case ' +
'a DoH address (https://dns.alidns.com/dns-query, https://doh.pub/dns-query) also works. ' +
- 'Support UDP, TCP, DoH, DoQ, DoT.'));
+ 'Support UDP, TCP, DoH, DoQ, DoT. A DoH/DoT address here also sends the bootstrap ' +
+ 'lookup back to the WAN resolver: only a bare IP can resolve the proxy DNS endpoint ' +
+ 'without the ISP.'));
o.value('wan', _('WAN DNS (read from interface)'));
o.value('223.5.5.5', _('Aliyun Public DNS (223.5.5.5)'));
o.value('210.2.4.8', _('CNNIC Public DNS (210.2.4.8)'));
@@ -201,16 +144,67 @@ function renderDnsCache(ctx) {
return true;
}
- o = s.taboption('dns_cache', form.Flag, 'cn_ip_fallback', _('CN-IP DNS fallback (sing-box 1.14)'),
+ o = s.taboption('dns', form.Flag, 'cn_ip_fallback', _('CN-IP DNS fallback (sing-box 1.14)'),
_('When the main DNS returns a mainland China IP, re-resolve via China DNS using evaluate/match_response.'));
o.depends('routing_mode', 'bypass_mainland_china');
o.rmempty = false;
- o = s.taboption('dns_cache', form.SectionValue, '_dns_cache', form.NamedSection, 'dns', 'homeproxy-pro');
- o.depends({'routing_mode': 'custom', '!reverse': true});
+ /* The 'dns' section, registered exactly once: see the file header. The
+ * custom-only options below keep their own depends() because the section
+ * is visible in both routing-mode families. */
+ o = s.taboption('dns', form.SectionValue, '_dns', form.NamedSection, 'dns', 'homeproxy-pro');
ss = o.subsection;
+ so = ss.option(form.ListValue, 'default_strategy', _('Default DNS strategy'),
+ _('The DNS strategy for resolving the domain name in the address.'));
+ for (let i in hp.dns_strategy)
+ so.value(i, hp.dns_strategy[i]);
+ so.depends('routing_mode', 'custom');
+
+ so = ss.option(form.ListValue, 'default_server', _('Default DNS server'));
+ so.load = function(section_id) {
+ delete this.keylist;
+ delete this.vallist;
+
+ this.value('default-dns', _('Default DNS (issued by WAN)'));
+ this.value('system-dns', _('System DNS'));
+ uci.sections('homeproxy-pro', 'dns_server', (res) => {
+ if (res.enabled !== '0')
+ this.value(res['.name'], res.label);
+ });
+
+ return this.super('load', section_id);
+ }
+ so.default = 'default-dns';
+ so.rmempty = false;
+ so.depends('routing_mode', 'custom');
+
+ so = ss.option(form.Flag, 'disable_cache', _('Disable DNS cache'));
+
+ so = ss.option(form.Flag, 'disable_cache_expire', _('Disable cache expire'));
+ so.depends('disable_cache', '0');
+
+ so = ss.option(form.Value, 'client_subnet', _('EDNS Client subnet'),
+ _('Append a edns0-subnet OPT extra record with the specified IP prefix to every query by default.
' +
+ 'If value is an IP address instead of prefix, /32 or /128 will be appended automatically.'));
+ so.datatype = 'or(cidr, ipaddr)';
+ so.depends('routing_mode', 'custom');
+
+ /* The four cache options are common to both families, so they are
+ * registered once, with no routing-mode dependency of their own. */
renderDnsCacheOptions(ss);
+ /* DNS settings end */
}
-return baseclass.extend({ renderDnsSettings, renderDnsRules, renderDnsCache });
+/* DNS rules sub-section. Split out from renderDnsSettings() so the
+ * orchestrator can call it after nodes.renderDnsServers() and preserve
+ * the original tab ordering (dns, dns_server, dns_rule). */
+function renderDnsRules(ctx) {
+ const { s, self } = ctx;
+
+ /* DNS rules start */
+ common.renderRuleSection(s, 'dns', self);
+ /* DNS rules end */
+}
+
+return baseclass.extend({ renderDnsSettings, renderDnsRules });
diff --git a/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js b/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js
index 3039a1668..f10d9489c 100644
--- a/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js
+++ b/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js
@@ -65,7 +65,7 @@ function renderRoutingNodes(ctx) {
this.value('default-dns', _('Default DNS (issued by WAN)'));
this.value('system-dns', _('System DNS'));
uci.sections('homeproxy-pro', 'dns_server', (res) => {
- if (res.enabled === '1')
+ if (res.enabled !== '0')
this.value(res['.name'], res.label);
});
@@ -96,7 +96,7 @@ function renderRoutingNodes(ctx) {
this.value('', _('Direct'));
uci.sections('homeproxy-pro', 'routing_node', (res) => {
- if (res['.name'] !== section_id && res.enabled === '1')
+ if (res['.name'] !== section_id && res.enabled !== '0')
this.value(res['.name'], res.label);
});
@@ -264,7 +264,7 @@ function renderDnsServers(ctx) {
so.modalonly = true;
so = ss.option(form.DynamicList, 'headers', _('Headers'),
- _('Additional headers to be sent to the DNS server.'));
+ _('Additional headers to be sent to the DNS server, one Name: Value per line.'));
so.depends('type', 'https');
so.depends('type', 'h3');
so.modalonly = true;
@@ -287,7 +287,7 @@ function renderDnsServers(ctx) {
this.value('default-dns', _('Default DNS (issued by WAN)'));
this.value('system-dns', _('System DNS'));
uci.sections('homeproxy-pro', 'dns_server', (res) => {
- if (res['.name'] !== section_id && res.enabled === '1')
+ if (res['.name'] !== section_id && res.enabled !== '0')
this.value(res['.name'], res.label);
});
@@ -324,7 +324,7 @@ function renderDnsServers(ctx) {
this.value('direct-out', _('Direct'));
uci.sections('homeproxy-pro', 'routing_node', (res) => {
- if (res.enabled === '1')
+ if (res.enabled !== '0')
this.value(res['.name'], res.label);
});
diff --git a/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js b/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js
index 842f8cf64..0a0d697ed 100644
--- a/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js
+++ b/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js
@@ -108,7 +108,11 @@ function render(ctx) {
let ports = [];
for (let i of value.split(',')) {
- if (!stubValidator.apply('port', i) && !stubValidator.apply('portrange', i))
+ /* A bare decimal port only: the backend's ports_to_nftarr()
+ * (firewall_utils.uc) keeps nothing else, so a range like
+ * "80-90" was accepted here and then dropped without a word -
+ * the port the user asked for was simply never proxied. */
+ if (!stubValidator.apply('port', i) || i === '0')
return _('Expecting: %s').format(_('valid port value'));
if (ports.includes(i))
return _('Port %s alrealy exists!').format(i);
@@ -210,7 +214,7 @@ function render(ctx) {
this.value('direct-out', _('Direct'));
this.value('block-out', _('Block'));
uci.sections('homeproxy-pro', 'routing_node', (res) => {
- if (res.enabled === '1')
+ if (res.enabled !== '0')
this.value(res['.name'], res.label);
});
@@ -228,7 +232,7 @@ function render(ctx) {
this.value('default-dns', _('Default DNS (issued by WAN)'));
this.value('system-dns', _('System DNS'));
uci.sections('homeproxy-pro', 'dns_server', (res) => {
- if (res.enabled === '1')
+ if (res.enabled !== '0')
this.value(res['.name'], res.label);
});
diff --git a/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js b/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js
index aed378dcf..ecaac7c5b 100644
--- a/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js
+++ b/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js
@@ -52,11 +52,16 @@ function render(ctx) {
so.default = 'remote';
so.rmempty = false;
- so = ss.option(form.ListValue, 'format', _('Format'));
+ so = ss.option(form.ListValue, 'format', _('Format'),
+ _('Leave empty to infer it from a .json or .srs path/url; sing-box requires it for any other extension.'));
+ so.value('', _('Automatic (by extension)'));
so.value('binary', _('Binary file'));
so.value('source', _('Source file'));
- so.default = 'binary';
- so.rmempty = false;
+ /* Not `binary` by default: sing-box only infers the format when the
+ * field is absent, and an explicit value wins. Defaulting to binary
+ * meant every local rule-set pointing at a .json file was parsed as a
+ * compiled .srs unless the user remembered to change the dropdown. */
+ so.rmempty = true;
so = ss.option(form.Value, 'path', _('Path'));
so.datatype = 'file';
@@ -99,7 +104,7 @@ function render(ctx) {
this.value('', _('Default'));
this.value('direct-out', _('Direct'));
uci.sections('homeproxy-pro', 'routing_node', (res) => {
- if (res.enabled === '1')
+ if (res.enabled !== '0')
this.value(res['.name'], res.label);
});
diff --git a/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/status.js b/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/status.js
index e30b3793d..8dd7c8428 100644
--- a/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/status.js
+++ b/luci-app-homeproxy-pro/htdocs/luci-static/resources/view/homeproxy-pro/status.js
@@ -98,6 +98,38 @@ function getConnStat(o, site) {
]);
}
+/* The gfwlist routing mode routes by an nft set that dnsmasq fills through
+ * `nftset=`. A LAN client with its own DoH/DoT never asks this router's DNS,
+ * so the set stays empty and the mode silently degrades to direct routing.
+ * The count is what tells "the mode is working" from "the mode is doing
+ * nothing", so it sits next to the resource versions. */
+function getGfwSetStat(o) {
+ return hp.rpcCall('gfw_set_counts', [], { expect: { '': {} } }).then((res) => {
+ let field = E('div', { 'style': 'cbi-value-field' });
+ let warn = false;
+
+ [ [ 'IPv4', res.v4 ], [ 'IPv6', res.v6 ] ].forEach(([ label, count ]) => {
+ let value;
+
+ if (count == null)
+ value = E('em', {}, _('not installed'));
+ else if (count === 0) {
+ value = E('strong', { 'style': 'color:orange' }, '%d'.format(count));
+ warn = warn || (label === 'IPv4');
+ } else
+ value = E('strong', { 'style': 'color:green' }, '%d'.format(count));
+
+ field.appendChild(E('span', {}, [ label + ': ', value ]));
+ field.appendChild(document.createTextNode(' '));
+ });
+
+ if (warn)
+ field.appendChild(E('em', {}, _('empty: clients may be bypassing this router\'s DNS')));
+
+ return field;
+ });
+}
+
function getResVersion(o, type) {
return hp.rpcCall('resources_get_version', [type],
{ params: ['type'], expect: { '': {} } }).then((res) => {
@@ -274,6 +306,9 @@ return view.extend({
o.cfgvalue = L.bind(getResVersion, this, o, 'gfw_list');
o.rawhtml = true;
+ o = s.option(form.DummyValue, '_gfw_set_size', _('GFW list addresses'));
+ o.cfgvalue = L.bind(getGfwSetStat, this, o);
+
o = s.option(form.Value, 'github_token', _('GitHub token'));
o.password = true;
o.renderWidget = function() {
diff --git a/luci-app-homeproxy-pro/po/templates/homeproxy-pro.pot b/luci-app-homeproxy-pro/po/templates/homeproxy-pro.pot
index 1364526cd..e532a1175 100644
--- a/luci-app-homeproxy-pro/po/templates/homeproxy-pro.pot
+++ b/luci-app-homeproxy-pro/po/templates/homeproxy-pro.pot
@@ -1,7 +1,13 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:222
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:48
+msgid ""
+"\"No filter\" proxies every LAN client, which is the default; the other two "
+"values select clients by the lists below."
+msgstr ""
+
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:254
msgid "%s log"
msgstr ""
@@ -13,11 +19,11 @@ msgstr ""
msgid "(empty)"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:110
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:142
msgid "(last updated %s)"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:111
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:143
msgid "(never updated on this device)"
msgstr ""
@@ -25,7 +31,7 @@ msgstr ""
msgid "/etc/homeproxy-pro/certs/..., /etc/acme/..., /etc/ssl/..."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:230
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:234
msgid ""
"1 matches the latest untagged evaluate result; other values match an "
"evaluate tag (1.14)."
@@ -35,8 +41,8 @@ msgstr ""
msgid "4 or 6. Not limited if empty."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:297
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:436
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:301
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:451
msgid ""
"%s will be temporarily overwritten to %s after 50 "
"triggers in 30s if not enabled."
@@ -85,7 +91,7 @@ msgstr ""
msgid "Access key secret"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:176
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:180
msgid "Action"
msgstr ""
@@ -118,7 +124,9 @@ msgid "Add a server"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:267
-msgid "Additional headers to be sent to the DNS server."
+msgid ""
+"Additional headers to be sent to the DNS server, one Name: Value"
+"code> per line."
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:249
@@ -152,7 +160,7 @@ msgstr ""
msgid "Alibaba Cloud DNS"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:173
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:116
msgid "Aliyun Public DNS (223.5.5.5)"
msgstr ""
@@ -181,11 +189,11 @@ msgstr ""
msgid "Allowed payload size is in the request."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:129
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:161
msgid "Already at the latest version."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:126
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:158
msgid "Already in updating."
msgstr ""
@@ -202,7 +210,7 @@ msgstr ""
msgid "Alternative TLS port"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:314
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:318
msgid "Answer"
msgstr ""
@@ -214,9 +222,9 @@ msgstr ""
msgid "AnyTLS padding scheme in array."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:223
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:460
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:89
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:227
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:475
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:188
msgid ""
"Append a edns0-subnet OPT extra record with the specified IP "
"prefix to every query by default.
If value is an IP address instead of "
@@ -264,6 +272,10 @@ msgstr ""
msgid "Auto update subscriptions and geodata."
msgstr ""
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:57
+msgid "Automatic (by extension)"
+msgstr ""
+
#: htdocs/luci-static/resources/homeproxy-pro.js:325
msgid "BBR"
msgstr ""
@@ -276,7 +288,7 @@ msgstr ""
msgid "BBR profile (1.14)"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:252
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:284
msgid "BaiDu"
msgstr ""
@@ -284,11 +296,11 @@ msgstr ""
msgid "Base64"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:170
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:174
msgid "Based on google/gvisor."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:56
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:58
msgid "Binary file"
msgstr ""
@@ -311,7 +323,7 @@ msgstr ""
msgid "Blacklist mode"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:211
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:215
msgid "Block"
msgstr ""
@@ -321,7 +333,7 @@ msgstr ""
msgid "Both"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:190
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:194
msgid "Bypass CN traffic"
msgstr ""
@@ -329,7 +341,7 @@ msgstr ""
msgid "Bypass mainland China"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:191
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:195
msgid "Bypass mainland China traffic via firewall rules by default."
msgstr ""
@@ -337,11 +349,11 @@ msgstr ""
msgid "CA provider"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:204
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:147
msgid "CN-IP DNS fallback (sing-box 1.14)"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:174
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:117
msgid "CNNIC Public DNS (210.2.4.8)"
msgstr ""
@@ -362,27 +374,27 @@ msgstr ""
msgid "Check"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:139
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:171
msgid "Check update"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:167
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:108
msgid "China DNS server"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:261
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:293
msgid "China IPv4 list version"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:265
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:297
msgid "China IPv6 list version"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:269
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:301
msgid "China list version"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:125
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:126
msgid "Chromium / Cronet"
msgstr ""
@@ -390,15 +402,15 @@ msgstr ""
msgid "Cipher suites"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:137
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:78
msgid "Cisco Public DNS (208.67.222.222)"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:231
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:263
msgid "Clean log"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:123
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:124
msgid "Client"
msgstr ""
@@ -410,7 +422,7 @@ msgstr ""
msgid "Client version"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:136
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:77
msgid "CloudFlare Public DNS (1.1.1.1)"
msgstr ""
@@ -420,7 +432,7 @@ msgstr ""
#: htdocs/luci-static/resources/view/homeproxy-pro/client.js:130
#: htdocs/luci-static/resources/view/homeproxy-pro/server.js:146
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:212
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:244
msgid "Collecting data..."
msgstr ""
@@ -432,7 +444,7 @@ msgstr ""
msgid "Congestion control algorithm"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:249
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:281
msgid "Connection check"
msgstr ""
@@ -464,8 +476,7 @@ msgstr ""
msgid "DNS Servers"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:56
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:122
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:59
msgid "DNS Settings"
msgstr ""
@@ -473,7 +484,7 @@ msgstr ""
msgid "DNS provider"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:39
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:42
msgid "DNS query timeout"
msgstr ""
@@ -481,7 +492,7 @@ msgstr ""
msgid "DNS rule"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:410
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:425
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:221
msgid "DNS server"
msgstr ""
@@ -494,49 +505,49 @@ msgstr ""
msgid "DTLS"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:179
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:211
msgid "Debug"
msgstr ""
#: htdocs/luci-static/resources/homeproxy-pro.js:62
#: htdocs/luci-static/resources/homeproxy-pro.js:334
#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:73
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:416
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:431
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:64
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:99
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:104
msgid "Default"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:197
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:417
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:71
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:201
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:432
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:169
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:65
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:287
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:228
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:232
msgid "Default DNS (issued by WAN)"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:66
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:164
msgid "Default DNS server"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:223
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:227
msgid "Default DNS server for resolving domain name in the server address."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:61
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:158
msgid "Default DNS strategy"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:203
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:207
msgid "Default outbound"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:222
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:226
msgid "Default outbound DNS"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:204
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:208
msgid "Default outbound for connections not matched by any routing rules."
msgstr ""
@@ -548,40 +559,39 @@ msgstr ""
msgid "Default server name"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:40
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:43
msgid "Default timeout per DNS query in seconds (sing-box default: 10)."
msgstr ""
#: htdocs/luci-static/resources/homeproxy-pro.js:130
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:335
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:339
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:97
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:325
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:210
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:100
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:214
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:105
msgid "Direct"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:165
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:166
msgid "Direct Domain List"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:54
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:99
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:55
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:100
msgid "Direct IPv4 IP-s"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:57
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:102
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:58
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:103
msgid "Direct IPv6 IP-s"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:60
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:61
msgid "Direct MAC-s"
msgstr ""
#: htdocs/luci-static/resources/homeproxy-pro.js:257
#: htdocs/luci-static/resources/homeproxy-pro.js:270
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:48
#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:26
#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:54
#: htdocs/luci-static/resources/view/homeproxy-pro/node.js:93
@@ -590,7 +600,7 @@ msgstr ""
msgid "Disable"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:209
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:213
msgid "Disable (the service)"
msgstr ""
@@ -605,12 +615,12 @@ msgid ""
"or the handshake otherwise fails."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:448
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:83
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:463
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:182
msgid "Disable DNS cache"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:449
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:464
msgid "Disable DNS cache in this query."
msgstr ""
@@ -622,32 +632,32 @@ msgstr ""
msgid "Disable TLS ALPN challenge"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:359
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:365
msgid "Disable UDP domain unmapping"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:212
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:216
msgid "Disable cache and save cache in this query."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:85
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:184
msgid "Disable cache expire"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:211
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:215
msgid "Disable dns cache"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:251
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:255
msgid "Disable optimistic DNS caching for this query."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:467
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:482
msgid "Disable optimistic DNS caching in this lookup (1.14)."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:250
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:466
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:254
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:481
msgid "Disable optimistic cache"
msgstr ""
@@ -655,15 +665,15 @@ msgstr ""
msgid "Disabled"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:487
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:502
msgid "Domain keyword"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:478
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:493
msgid "Domain name"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:491
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:506
msgid "Domain regex"
msgstr ""
@@ -672,15 +682,15 @@ msgid "Domain resolver"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:77
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:198
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:202
msgid "Domain strategy"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:442
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:457
msgid "Domain strategy for resolving the domain names."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:483
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:498
msgid "Domain suffix"
msgstr ""
@@ -688,11 +698,11 @@ msgstr ""
msgid "Domains"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:435
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:450
msgid "Don't drop packets"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:296
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:300
msgid "Don't drop requests"
msgstr ""
@@ -704,13 +714,13 @@ msgstr ""
msgid "Download bandwidth in Mbps."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:431
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:437
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:446
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:452
msgid "Drop packets"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:291
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:298
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:295
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:302
msgid "Drop requests"
msgstr ""
@@ -752,9 +762,9 @@ msgstr ""
msgid "ECH key"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:222
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:459
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:88
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:226
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:474
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:187
msgid "EDNS Client subnet"
msgstr ""
@@ -823,11 +833,11 @@ msgstr ""
msgid "Enable connection reuse (the Snell v2 CONNECT command)."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:175
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:179
msgid "Enable endpoint-independent NAT"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:195
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:199
msgid ""
"Enable neighbor resolution so rules can match LAN devices by hostname (1.14)."
msgstr ""
@@ -856,15 +866,15 @@ msgstr ""
msgid "Endpoint independent"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:182
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:214
msgid "Error"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:181
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:185
msgid "Evaluate (1.14)"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:234
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:238
msgid "Evaluate tag"
msgstr ""
@@ -873,28 +883,30 @@ msgstr ""
#: htdocs/luci-static/resources/homeproxy-pro.js:1093
#: htdocs/luci-static/resources/homeproxy-pro.js:1106
#: htdocs/luci-static/resources/homeproxy-pro.js:1125
-#: htdocs/luci-static/resources/homeproxy-pro.js:1134
-#: htdocs/luci-static/resources/homeproxy-pro.js:1145
+#: htdocs/luci-static/resources/homeproxy-pro.js:1140
#: htdocs/luci-static/resources/homeproxy-pro.js:1154
-#: htdocs/luci-static/resources/homeproxy-pro.js:1156
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:158
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:203
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:145
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:157
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:161
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:183
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:194
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:198
+#: htdocs/luci-static/resources/homeproxy-pro.js:1163
+#: htdocs/luci-static/resources/homeproxy-pro.js:1174
+#: htdocs/luci-static/resources/homeproxy-pro.js:1183
+#: htdocs/luci-static/resources/homeproxy-pro.js:1185
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:159
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:204
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:86
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:98
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:102
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:126
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:137
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:141
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:141
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:155
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:158
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:112
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:72
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:75
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:116
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:77
#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:80
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:83
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:123
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:126
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:85
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:88
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:128
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:131
#: htdocs/luci-static/resources/view/homeproxy-pro/node.js:484
#: htdocs/luci-static/resources/view/homeproxy-pro/node.js:741
#: htdocs/luci-static/resources/view/homeproxy-pro/node.js:744
@@ -915,16 +927,16 @@ msgstr ""
msgid "External account key ID"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:324
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:328
msgid "Extra records"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:117
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:122
msgid ""
"Extra rule-set tags sharing these options. Requires {tag} in path/url (1.14)."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:116
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:121
msgid "Extra tags"
msgstr ""
@@ -936,8 +948,8 @@ msgstr ""
msgid "Failed to generate %s, error: %s."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:124
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:177
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:125
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:178
msgid "Failed to read domain list: %s."
msgstr ""
@@ -945,7 +957,7 @@ msgstr ""
msgid "Failed to upload %s, error: %s."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:183
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:215
msgid "Fatal"
msgstr ""
@@ -957,11 +969,11 @@ msgstr ""
msgid "Filter nodes"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:194
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:198
msgid "Find neighbor hosts"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:126
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:127
msgid "Firefox / uquic firefox"
msgstr ""
@@ -982,16 +994,16 @@ msgstr ""
msgid "Format"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:376
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:385
msgid "Fragment TLS handshake into multiple TLS records."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:381
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:392
msgid ""
"Fragment TLS handshakes. Due to poor performance, try %s first."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:386
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:399
msgid "Fragment fallback delay"
msgstr ""
@@ -999,7 +1011,11 @@ msgstr ""
msgid "GET"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:273
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:309
+msgid "GFW list addresses"
+msgstr ""
+
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:305
msgid "GFW list version"
msgstr ""
@@ -1007,15 +1023,15 @@ msgstr ""
msgid "GFWList"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:72
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:73
msgid "Gaming mode IPv4 IP-s"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:74
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:75
msgid "Gaming mode IPv6 IP-s"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:77
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:78
msgid "Gaming mode MAC-s"
msgstr ""
@@ -1029,7 +1045,7 @@ msgstr ""
msgid "Generate"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:277
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:312
msgid "GitHub token"
msgstr ""
@@ -1041,15 +1057,15 @@ msgstr ""
msgid "Global padding"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:79
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:80
msgid "Global proxy IPv4 IP-s"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:82
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:83
msgid "Global proxy IPv6 IP-s"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:85
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:86
msgid "Global proxy MAC-s"
msgstr ""
@@ -1057,11 +1073,11 @@ msgstr ""
msgid "Global settings"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:255
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:287
msgid "Google"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:138
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:79
msgid "Google Public DNS (8.8.8.8)"
msgstr ""
@@ -1133,7 +1149,7 @@ msgstr ""
#: htdocs/luci-static/resources/view/homeproxy-pro/client.js:70
#: htdocs/luci-static/resources/view/homeproxy-pro/client.js:119
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:299
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:334
#: root/usr/share/luci/menu.d/luci-app-homeproxy-pro.json:3
msgid "HomeProxy"
msgstr ""
@@ -1175,7 +1191,7 @@ msgid ""
"command (in seconds)."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:400
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:414
msgid "How the forged segment is rejected by the real server."
msgstr ""
@@ -1187,7 +1203,7 @@ msgstr ""
msgid "Hysteria2"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:503
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:519
msgid "IP CIDR"
msgstr ""
@@ -1211,7 +1227,7 @@ msgstr ""
msgid "IPv6 only"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:135
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:139
msgid "IPv6 support"
msgstr ""
@@ -1228,13 +1244,13 @@ msgstr ""
msgid "Idle timeout"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:365
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:372
msgid ""
"If enabled, attempts to connect UDP connection to the destination instead of "
"listen."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:360
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:366
msgid ""
"If enabled, for UDP proxy requests addressed to a domain, the original "
"packet address will be sent in the response instead of the mapped domain."
@@ -1246,7 +1262,7 @@ msgid ""
"connections."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:199
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:203
msgid ""
"If set, the requested domain name will be resolved to IP before routing."
msgstr ""
@@ -1283,7 +1299,7 @@ msgid ""
"manually."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:183
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:187
#: htdocs/luci-static/resources/view/homeproxy-pro/server.js:615
msgid "In seconds."
msgstr ""
@@ -1303,15 +1319,15 @@ msgid ""
"seconds."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:180
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:212
msgid "Info"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:110
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:115
msgid "Initial path"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:394
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:407
msgid ""
"Inject a forged TLS ClientHello carrying this SNI before the real one to "
"fool SNI-filtering middleboxes. Requires elevated privileges."
@@ -1339,15 +1355,15 @@ msgid ""
"seconds)."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:160
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:161
msgid "Invert"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:161
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:162
msgid "Invert match result."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:240
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:244
msgid ""
"Judge this response-dependent rule in parallel; first match wins (1.14)."
msgstr ""
@@ -1369,6 +1385,12 @@ msgstr ""
msgid "Label"
msgstr ""
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:56
+msgid ""
+"Leave empty to infer it from a .json or .srs path/"
+"url; sing-box requires it for any other extension."
+msgstr ""
+
#: htdocs/luci-static/resources/view/homeproxy-pro/node.js:258
#: htdocs/luci-static/resources/view/homeproxy-pro/server.js:305
msgid ""
@@ -1376,7 +1398,7 @@ msgid ""
"compatibility purposes only, use of alterId > 1 is not recommended."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:172
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:176
msgid "Less compatibility and sometimes better performance."
msgstr ""
@@ -1399,15 +1421,15 @@ msgstr ""
msgid "List of supported application level protocols, in order of preference."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:315
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:319
msgid "List of text DNS record to respond as answers."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:325
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:329
msgid "List of text DNS record to respond as extra records."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:320
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:324
msgid "List of text DNS record to respond as name servers."
msgstr ""
@@ -1423,7 +1445,7 @@ msgstr ""
msgid "Listen port"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:210
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:242
msgid "Loading"
msgstr ""
@@ -1435,7 +1457,7 @@ msgstr ""
msgid "Local address"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:111
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:116
msgid ""
"Local file with initial rule-set content; avoids blocking startup on first "
"download (1.14)."
@@ -1461,11 +1483,11 @@ msgstr ""
msgid "Main node"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:157
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:158
msgid "Make IP CIDR in rule set used to match the source IP."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:157
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:158
msgid "Make IP CIDR in rule sets match the source IP."
msgstr ""
@@ -1473,78 +1495,78 @@ msgstr ""
msgid "Masquerade"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:528
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:544
msgid "Match DNSSEC OK"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:504
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:520
msgid ""
"Match IP CIDR with the evaluated response (needs match_response; old rules "
"are auto-wrapped in 1.14)."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:505
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:521
msgid "Match IP CIDR."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:514
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:530
msgid "Match LAN device by MAC address."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:519
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:535
msgid ""
"Match LAN device hostname via neighbor resolution; enable find_neighbor."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:484
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:499
msgid "Match domain suffix."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:488
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:503
msgid "Match domain using keyword."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:492
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:507
msgid "Match domain using regular expression."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:479
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:494
msgid "Match full domain."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:549
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:565
msgid "Match port range. Format as START:/:END/START:END."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:544
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:560
msgid "Match port."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:509
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:525
msgid "Match private IP"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:510
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:526
msgid "Match private IP with the evaluated response (needs match_response)."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:500
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:516
msgid "Match private source IP"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:554
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:570
msgid "Match process name."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:562
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:578
msgid "Match process path using regular expression."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:558
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:574
msgid "Match process path."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:529
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:545
msgid "Match queries with the DNSSEC OK bit set (1.14)."
msgstr ""
@@ -1552,44 +1574,44 @@ msgstr ""
msgid "Match query type."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:229
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:233
msgid "Match response"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:141
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:142
msgid ""
"Match rule set. DNS: rules referencing rule-sets that contain query_type are "
"incompatible with legacy address filters (ip_cidr / ip_is_private) — use "
"match_response instead (1.14)."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:142
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:143
msgid ""
"Match rule set. Since 1.14: a rule-set holding a single default rule is "
"merged into this rule; any other rule-set matches as an OR collection."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:496
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:512
msgid "Match source IP CIDR."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:539
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:555
msgid "Match source port range. Format as START:/:END/START:END."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:534
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:550
msgid "Match source port."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:524
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:540
msgid "Match the EDNS Client Subnet in the query (1.14)."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:270
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:274
msgid "Match the evaluated response code (requires match_response)."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:137
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:138
msgid "Match user name."
msgstr ""
@@ -1609,7 +1631,7 @@ msgstr ""
msgid "Max obfs packet size (1.14)"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:37
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:40
msgid "Max time an expired entry may be served. Examples: 3d, 1h."
msgstr ""
@@ -1655,10 +1677,10 @@ msgid "Maximum streams"
msgstr ""
#: htdocs/luci-static/resources/homeproxy-pro.js:581
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:289
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:298
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:429
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:437
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:293
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:302
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:444
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:452
msgid "Method"
msgstr ""
@@ -1689,11 +1711,11 @@ msgid "Minimum streams"
msgstr ""
#: htdocs/luci-static/resources/homeproxy-pro.js:136
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:157
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:161
msgid "Mixed"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:168
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:172
msgid "Mixed system TCP stack and gVisor UDP stack."
msgstr ""
@@ -1722,7 +1744,7 @@ msgstr ""
msgid "NOTE: Save current settings before updating subscriptions."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:319
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:323
msgid "NS"
msgstr ""
@@ -1752,6 +1774,10 @@ msgstr ""
msgid "No additional encryption support: It's basically duplicate encryption."
msgstr ""
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:49
+msgid "No filter (proxy every client)"
+msgstr ""
+
#: htdocs/luci-static/resources/view/homeproxy-pro/node.js:797
msgid "No subscription available"
msgstr ""
@@ -1801,8 +1827,8 @@ msgstr ""
msgid "Obfuscation mode"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:114
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:171
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:115
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:172
msgid ""
"One domain per line. No spaces, no comments, no wildcards; the backend "
"rejects invalid characters and tells you which line."
@@ -1816,15 +1842,15 @@ msgstr ""
msgid "Only proxy mainland China"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:30
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:33
msgid "Optimistic DNS cache"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:36
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:39
msgid "Optimistic cache timeout"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:235
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:239
msgid "Optional tag for this evaluate result."
msgstr ""
@@ -1836,10 +1862,10 @@ msgstr ""
msgid "Other fields"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:329
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:333
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:91
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:319
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:93
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:98
msgid "Outbound"
msgstr ""
@@ -1847,36 +1873,36 @@ msgstr ""
msgid "Outbound node"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:94
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:99
msgid ""
"Outbound used to download this rule-set (via sing-box 1.14 http_clients)."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:347
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:351
msgid "Override address"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:472
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:487
msgid "Override dns.timeout for this lookup, in seconds (1.14)."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:257
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:261
msgid "Override dns.timeout for this query, in seconds."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:353
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:358
msgid "Override port"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:348
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:352
msgid "Override the connection destination address."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:354
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:359
msgid "Override the connection destination port."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:131
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:72
msgid "Overseas DNS server"
msgstr ""
@@ -1896,7 +1922,7 @@ msgstr ""
msgid "Padding scheme"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:184
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:216
msgid "Panic"
msgstr ""
@@ -1908,7 +1934,7 @@ msgstr ""
#: htdocs/luci-static/resources/homeproxy-pro.js:576
#: htdocs/luci-static/resources/homeproxy-pro.js:610
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:259
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:61
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:66
msgid "Path"
msgstr ""
@@ -1916,13 +1942,13 @@ msgstr ""
msgid "Peer pubkic key"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:176
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:180
msgid ""
"Performance may degrade slightly, so it is not recommended to enable on when "
"it is not needed."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:44
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:47
msgid "Persist DNS cache across restarts (sing-box 1.14, replaces Store RDRC)."
msgstr ""
@@ -1942,13 +1968,13 @@ msgstr ""
msgid "Plugin opts"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:543
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:559
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:254
#: htdocs/luci-static/resources/view/homeproxy-pro/node.js:63
msgid "Port"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:114
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:118
msgid "Port %s alrealy exists!"
msgstr ""
@@ -1960,7 +1986,7 @@ msgstr ""
msgid "Port hopping interval in seconds."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:548
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:564
msgid "Port range"
msgstr ""
@@ -1968,7 +1994,7 @@ msgstr ""
msgid "Pre-shared key"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:180
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:184
msgid "Predefined"
msgstr ""
@@ -1997,15 +2023,15 @@ msgstr ""
msgid "Process fields"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:553
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:569
msgid "Process name"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:557
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:573
msgid "Process path"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:561
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:577
msgid "Process path (regex)"
msgstr ""
@@ -2026,25 +2052,25 @@ msgid ""
"default in v2ray and cannot be disabled)."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:108
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:109
msgid "Proxy Domain List"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:63
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:92
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:64
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:93
msgid "Proxy IPv4 IP-s"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:66
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:95
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:67
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:96
msgid "Proxy IPv6 IP-s"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:69
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:70
msgid "Proxy MAC-s"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:50
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:51
msgid "Proxy all except listed"
msgstr ""
@@ -2052,11 +2078,11 @@ msgstr ""
msgid "Proxy filter mode"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:49
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:50
msgid "Proxy listed only"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:122
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:126
msgid "Proxy mode"
msgstr ""
@@ -2075,12 +2101,12 @@ msgstr ""
msgid "QUIC congestion control algorithm."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:523
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:539
msgid "Query EDNS client subnet"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:256
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:471
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:260
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:486
msgid "Query timeout"
msgstr ""
@@ -2088,7 +2114,7 @@ msgstr ""
msgid "Query type"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:302
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:306
msgid "RCode"
msgstr ""
@@ -2119,7 +2145,7 @@ msgstr ""
msgid "RUNNING"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:239
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:243
msgid "Race"
msgstr ""
@@ -2135,19 +2161,19 @@ msgstr ""
msgid "Recursive resolver detected!"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:123
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:127
msgid "Redirect TCP"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:125
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:129
msgid "Redirect TCP + TProxy UDP"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:127
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:131
msgid "Redirect TCP + Tun UDP"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:236
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:268
msgid "Refresh every %s seconds."
msgstr ""
@@ -2155,7 +2181,7 @@ msgstr ""
msgid "Region ID"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:178
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:182
msgid "Reject"
msgstr ""
@@ -2167,7 +2193,7 @@ msgstr ""
msgid "Remove %s nodes"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:263
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:267
msgid "Remove EDNS client subnet"
msgstr ""
@@ -2175,15 +2201,15 @@ msgstr ""
msgid "Remove all nodes from subscriptions"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:264
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:268
msgid "Remove the edns0-subnet record from the query (1.14)."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:290
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:294
msgid "Reply with REFUSED"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:430
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:445
msgid "Reply with TCP RST / ICMP port unreachable"
msgstr ""
@@ -2191,15 +2217,15 @@ msgstr ""
msgid "Reserved field bytes"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:184
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:188
msgid "Resolve"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:441
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:456
msgid "Resolve strategy"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:132
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:73
msgid ""
"Resolves every domain that is not on the China DNS path, through the "
"selected proxy node. A domestic resolver answers these queries with polluted "
@@ -2208,35 +2234,37 @@ msgid ""
"Support UDP, TCP, DoH, DoQ, DoT. TCP protocol will be used if not specified."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:168
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:109
msgid ""
"Resolves the domains on the China path, so it has to be a domestic service "
"to get the correct local CDN answers. Keep the default; the upstream may "
"block UDP/53, in which case a DoH address (https://dns.alidns.com/dns-query, "
-"https://doh.pub/dns-query) also works. Support UDP, TCP, DoH, DoQ, DoT."
+"https://doh.pub/dns-query) also works. Support UDP, TCP, DoH, DoQ, DoT. A "
+"DoH/DoT address here also sends the bootstrap lookup back to the WAN "
+"resolver: only a bare IP can resolve the proxy DNS endpoint without the ISP."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:258
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:290
msgid "Resources management"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:281
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:285
msgid "Response NS"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:269
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:273
msgid "Response RCode"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:276
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:280
msgid "Response answer"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:285
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:289
msgid "Response extra"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:31
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:34
msgid ""
"Return expired cache immediately and refresh in background (sing-box 1.14)."
msgstr ""
@@ -2249,21 +2277,21 @@ msgstr ""
msgid "Reuse listener address."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:216
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:453
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:220
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:468
msgid "Rewrite TTL"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:217
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:454
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:221
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:469
msgid "Rewrite TTL in DNS responses."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:177
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:181
msgid "Route"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:183
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:187
msgid "Route options"
msgstr ""
@@ -2299,16 +2327,16 @@ msgstr ""
msgid "Rule Set"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:140
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:141
#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:31
msgid "Rule set"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:156
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:157
msgid "Rule set IP CIDR as source IP"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:68
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:73
msgid "Rule set URL"
msgstr ""
@@ -2329,7 +2357,7 @@ msgstr ""
msgid "SUoT version"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:128
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:129
msgid "Safari / Apple Network API"
msgstr ""
@@ -2341,8 +2369,8 @@ msgstr ""
msgid "Same as main node"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:284
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:290
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:319
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:325
msgid "Save"
msgstr ""
@@ -2354,11 +2382,11 @@ msgstr ""
msgid "Save subscriptions settings"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:245
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:249
msgid "Send the query before pending race rules are judged (1.14)."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:191
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:195
#: htdocs/luci-static/resources/view/homeproxy-pro/server.js:176
msgid "Server"
msgstr ""
@@ -2410,7 +2438,7 @@ msgstr ""
msgid "Snell version"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:124
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:125
msgid "Sniffed client type (QUIC client type or SSH client name)."
msgstr ""
@@ -2420,7 +2448,7 @@ msgid ""
"box.sagernet.org/configuration/route/sniff/\">Sniff for details."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:143
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:147
msgid "Sniffer: advanced mode (100ms, universal list)"
msgstr ""
@@ -2444,31 +2472,31 @@ msgstr ""
msgid "Socks5"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:495
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:511
msgid "Source IP CIDR"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:513
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:529
msgid "Source MAC address"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:57
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:59
msgid "Source file"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:518
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:534
msgid "Source hostname"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:533
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:549
msgid "Source port"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:538
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:554
msgid "Source port range"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:411
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:426
msgid ""
"Specifies DNS server tag to use instead of selecting through DNS routing."
msgstr ""
@@ -2502,7 +2530,7 @@ msgid ""
"commas."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:244
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:248
msgid "Speculative"
msgstr ""
@@ -2510,7 +2538,7 @@ msgstr ""
msgid "Standard (default)"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:43
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:46
msgid "Store DNS cache"
msgstr ""
@@ -2550,7 +2578,7 @@ msgstr ""
msgid "Successfully imported %s nodes of total %s."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:120
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:152
msgid "Successfully updated."
msgstr ""
@@ -2561,16 +2589,16 @@ msgid ""
"online configuration delivery standard."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:160
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:164
msgid "System"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:198
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:418
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:72
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:202
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:433
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:170
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:66
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:288
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:229
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:233
msgid "System DNS"
msgstr ""
@@ -2585,11 +2613,11 @@ msgstr ""
msgid "TCP fast open"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:154
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:158
msgid "TCP/IP stack"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:155
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:159
msgid "TCP/IP stack."
msgstr ""
@@ -2608,7 +2636,7 @@ msgstr ""
msgid "TLS SNI"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:380
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:391
msgid "TLS fragment"
msgstr ""
@@ -2620,16 +2648,16 @@ msgstr ""
msgid "TLS is not enforced. If TLS is not configured, plain HTTP 1.1 is used."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:375
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:382
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:384
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:393
msgid "TLS record fragment"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:393
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:406
msgid "TLS spoof SNI (1.14)"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:399
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:413
msgid "TLS spoof method (1.14)"
msgstr ""
@@ -2655,11 +2683,11 @@ msgstr ""
msgid "Tag of an outbound for connecting to the dns server."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:192
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:196
msgid "Tag of the target dns server."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:330
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:334
msgid "Tag of the target outbound."
msgstr ""
@@ -2668,7 +2696,7 @@ msgid ""
"Tell the client to use the BBR flow control algorithm instead of Hysteria CC."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:175
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:118
msgid "Tencent Public DNS (119.29.29.29)"
msgstr ""
@@ -2692,7 +2720,7 @@ msgstr ""
msgid "Test tolerance"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:277
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:281
msgid "Text DNS records to match in the evaluated answer."
msgstr ""
@@ -2700,7 +2728,7 @@ msgstr ""
msgid "The ACME CA provider to use."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:62
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:159
msgid "The DNS strategy for resolving the domain name in the address."
msgstr ""
@@ -2762,7 +2790,7 @@ msgid ""
"account."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:387
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:400
msgid ""
"The fallback value in milliseconds used when TLS segmentation cannot "
"automatically determine the wait time."
@@ -2824,7 +2852,7 @@ msgstr ""
msgid "The request %s failed: %s."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:303
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:307
msgid "The response code."
msgstr ""
@@ -2874,11 +2902,11 @@ msgid ""
"QUIC stream based UDP relay mode that TUIC does not provide."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:176
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:119
msgid "ThreatBook Public DNS (117.50.10.10)"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:370
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:378
msgid ""
"Timeout for UDP connections.
Setting a larger value than the UDP timeout "
"in inbounds will have no effect."
@@ -2890,13 +2918,13 @@ msgid ""
"code>."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:130
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:134
msgid ""
"To enable Tun support, you need to install ip-full and "
"kmod-tun"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:178
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:210
msgid "Trace"
msgstr ""
@@ -2912,7 +2940,7 @@ msgstr ""
msgid "Tuic"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:128
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:132
msgid "Tun TCP/UDP"
msgstr ""
@@ -2937,7 +2965,7 @@ msgstr ""
msgid "UDP NAT Settings"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:182
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:186
#: htdocs/luci-static/resources/view/homeproxy-pro/server.js:614
msgid "UDP NAT expiration time"
msgstr ""
@@ -2970,7 +2998,7 @@ msgstr ""
msgid "UDP relay mode"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:369
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:377
msgid "UDP timeout"
msgstr ""
@@ -2990,7 +3018,7 @@ msgstr ""
msgid "UUID"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:132
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:164
msgid "Unknown error."
msgstr ""
@@ -3010,15 +3038,15 @@ msgstr ""
msgid "Update %s subscriptions"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:123
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:155
msgid "Update failed."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:132
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:137
msgid "Update interval"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:133
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:138
msgid "Update interval of rule set."
msgstr ""
@@ -3075,7 +3103,7 @@ msgstr ""
msgid "Use ACME TLS certificate issuer."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:144
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:148
msgid ""
"Use the tutorial-recommended sniffer profile (100ms timeout, http/tls/stun/"
"quic/dns). Default keeps the 300ms / unconstrained-list behaviour."
@@ -3091,7 +3119,7 @@ msgstr ""
msgid "Used to verify the hostname on the returned certificates."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:136
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:137
msgid "User"
msgstr ""
@@ -3116,15 +3144,15 @@ msgstr ""
msgid "VMess"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:172
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:115
msgid "WAN DNS (read from interface)"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:90
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:91
msgid "WAN IP Policy"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:181
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:213
msgid "Warn"
msgstr ""
@@ -3132,7 +3160,7 @@ msgstr ""
msgid "WebSocket"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:205
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:148
msgid ""
"When the main DNS returns a mainland China IP, re-resolve via China DNS "
"using evaluate/match_response."
@@ -3175,12 +3203,16 @@ msgstr ""
msgid "ZeroSSL"
msgstr ""
+#: htdocs/luci-static/resources/homeproxy-pro.js:1154
+msgid "a numeric code or an uppercase type name (for example AAAA)"
+msgstr ""
+
#: htdocs/luci-static/resources/view/homeproxy-pro/node.js:438
#: htdocs/luci-static/resources/view/homeproxy-pro/server.js:527
msgid "certificate"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:364
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:371
msgid "connect UDP connections"
msgstr ""
@@ -3189,6 +3221,10 @@ msgstr ""
msgid "default"
msgstr ""
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:127
+msgid "empty: clients may be bypassing this router's DNS"
+msgstr ""
+
#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:91
msgid "failed"
msgstr ""
@@ -3205,7 +3241,7 @@ msgstr ""
msgid "gRPC service name"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:158
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:162
msgid "gVisor"
msgstr ""
@@ -3215,12 +3251,12 @@ msgid "http"
msgstr ""
#: htdocs/luci-static/resources/homeproxy-pro.js:220
-#: htdocs/luci-static/resources/homeproxy-pro.js:1134
-#: htdocs/luci-static/resources/homeproxy-pro.js:1154
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:145
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:183
+#: htdocs/luci-static/resources/homeproxy-pro.js:1163
+#: htdocs/luci-static/resources/homeproxy-pro.js:1183
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:86
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:126
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:141
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:72
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:77
#: htdocs/luci-static/resources/view/homeproxy-pro/node.js:484
msgid "non-empty value"
msgstr ""
@@ -3233,6 +3269,10 @@ msgstr ""
msgid "none"
msgstr ""
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:115
+msgid "not installed"
+msgstr ""
+
#: htdocs/luci-static/resources/homeproxy-pro.js:632
#: htdocs/luci-static/resources/view/homeproxy-pro/node.js:776
msgid "packet addr (v2ray-core v5+)"
@@ -3246,11 +3286,11 @@ msgstr ""
msgid "private key"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:127
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:128
msgid "quic-go / uquic chrome"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:302
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:337
msgid "sing-box client"
msgstr ""
@@ -3272,7 +3312,7 @@ msgid ""
"(Password above) must be 12-255 bytes for v6."
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:305
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:340
msgid "sing-box server"
msgstr ""
@@ -3294,7 +3334,7 @@ msgstr ""
msgid "unique UCI identifier"
msgstr ""
-#: htdocs/luci-static/resources/homeproxy-pro.js:1145
+#: htdocs/luci-static/resources/homeproxy-pro.js:1174
msgid "unique value"
msgstr ""
@@ -3331,17 +3371,17 @@ msgstr ""
msgid "v6"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:157
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:161
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:194
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:198
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:98
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:102
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:137
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:141
msgid "valid DNS server address"
msgstr ""
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:155
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:158
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:80
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:83
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:85
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:88
#: htdocs/luci-static/resources/view/homeproxy-pro/node.js:741
#: htdocs/luci-static/resources/view/homeproxy-pro/node.js:744
msgid "valid URL"
@@ -3351,8 +3391,8 @@ msgstr ""
msgid "valid base64 key with %d characters"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:158
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:203
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:159
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:204
msgid "valid hostname"
msgstr ""
@@ -3360,39 +3400,43 @@ msgstr ""
msgid "valid port range (port1:port2)"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:112
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:116
msgid "valid port value"
msgstr ""
-#: htdocs/luci-static/resources/homeproxy-pro.js:1156
+#: htdocs/luci-static/resources/homeproxy-pro.js:1140
+msgid "valid regular expression"
+msgstr ""
+
+#: htdocs/luci-static/resources/homeproxy-pro.js:1185
msgid "valid uuid"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:403
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:417
msgid "wrong-ack"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:402
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:416
msgid "wrong-checksum"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:404
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:418
msgid "wrong-md5"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:401
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:415
msgid "wrong-sequence (default)"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:405
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:419
msgid "wrong-timestamp"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:75
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:123
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:80
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:128
msgid "{tag} in URL when extra tags are used"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:126
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:131
msgid "{tag} in initial path when extra tags are used"
msgstr ""
diff --git a/luci-app-homeproxy-pro/po/zh_Hans/homeproxy-pro.po b/luci-app-homeproxy-pro/po/zh_Hans/homeproxy-pro.po
index 67a52dc6e..21c10d9f8 100644
--- a/luci-app-homeproxy-pro/po/zh_Hans/homeproxy-pro.po
+++ b/luci-app-homeproxy-pro/po/zh_Hans/homeproxy-pro.po
@@ -8,7 +8,13 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:222
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:48
+msgid ""
+"\"No filter\" proxies every LAN client, which is the default; the other two "
+"values select clients by the lists below."
+msgstr "“不过滤”会代理全部 LAN 客户端(默认值);另外两个取值按下方列表筛选客户端。"
+
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:254
msgid "%s log"
msgstr "%s 日志"
@@ -20,11 +26,11 @@ msgstr "移除了 %s 个节点"
msgid "(empty)"
msgstr "(空)"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:110
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:142
msgid "(last updated %s)"
msgstr "(最后更新 %s)"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:111
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:143
msgid "(never updated on this device)"
msgstr "(本设备从未更新)"
@@ -32,7 +38,7 @@ msgstr "(本设备从未更新)"
msgid "/etc/homeproxy-pro/certs/..., /etc/acme/..., /etc/ssl/..."
msgstr "/etc/homeproxy-pro/certs/...,/etc/acme/...,/etc/ssl/..."
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:230
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:234
msgid ""
"1 matches the latest untagged evaluate result; other values match an "
"evaluate tag (1.14)."
@@ -44,8 +50,8 @@ msgstr ""
msgid "4 or 6. Not limited if empty."
msgstr "4 或 6。留空不限制。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:297
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:436
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:301
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:451
msgid ""
"%s will be temporarily overwritten to %s after 50 "
"triggers in 30s if not enabled."
@@ -95,7 +101,7 @@ msgstr "访问密钥 ID"
msgid "Access key secret"
msgstr "访问密钥 Secret"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:176
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:180
msgid "Action"
msgstr "操作"
@@ -128,8 +134,10 @@ msgid "Add a server"
msgstr "新增服务器"
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:267
-msgid "Additional headers to be sent to the DNS server."
-msgstr "发送到 DNS 服务器的附加标头。"
+msgid ""
+"Additional headers to be sent to the DNS server, one Name: Value"
+"code> per line."
+msgstr "发送到 DNS 服务器的附加标头,每行一个 Name: Value。"
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:249
#: htdocs/luci-static/resources/view/homeproxy-pro/node.js:58
@@ -162,7 +170,7 @@ msgstr "激进"
msgid "Alibaba Cloud DNS"
msgstr "阿里云 DNS"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:173
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:116
msgid "Aliyun Public DNS (223.5.5.5)"
msgstr "阿里云公共 DNS(223.5.5.5)"
@@ -191,11 +199,11 @@ msgstr "从订阅获取节点时,默认允许不安全连接。"
msgid "Allowed payload size is in the request."
msgstr "请求中允许的载荷大小。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:129
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:161
msgid "Already at the latest version."
msgstr "已是最新版本。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:126
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:158
msgid "Already in updating."
msgstr "已在更新中。"
@@ -212,7 +220,7 @@ msgstr "替代 HTTP 端口"
msgid "Alternative TLS port"
msgstr "替代 HTTPS 端口"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:314
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:318
msgid "Answer"
msgstr "回答"
@@ -224,9 +232,9 @@ msgstr "AnyTLS"
msgid "AnyTLS padding scheme in array."
msgstr "AnyTLS 填充方案。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:223
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:460
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:89
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:227
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:475
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:188
msgid ""
"Append a edns0-subnet OPT extra record with the specified IP "
"prefix to every query by default.
If value is an IP address instead of "
@@ -277,6 +285,10 @@ msgstr "自动更新"
msgid "Auto update subscriptions and geodata."
msgstr "自动更新订阅和地理数据。"
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:57
+msgid "Automatic (by extension)"
+msgstr "自动(按扩展名)"
+
#: htdocs/luci-static/resources/homeproxy-pro.js:325
msgid "BBR"
msgstr "BBR"
@@ -289,7 +301,7 @@ msgstr "BBR 拥塞控制算法档位。仅 Hysteria2。"
msgid "BBR profile (1.14)"
msgstr "BBR 档位(1.14)"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:252
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:284
msgid "BaiDu"
msgstr "百度"
@@ -297,11 +309,11 @@ msgstr "百度"
msgid "Base64"
msgstr "Base64"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:170
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:174
msgid "Based on google/gvisor."
msgstr "基于 google/gvisor。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:56
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:58
msgid "Binary file"
msgstr "二进制文件"
@@ -324,7 +336,7 @@ msgstr "BitTorrent"
msgid "Blacklist mode"
msgstr "黑名单模式"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:211
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:215
msgid "Block"
msgstr "封锁"
@@ -334,7 +346,7 @@ msgstr "封锁"
msgid "Both"
msgstr "全部"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:190
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:194
msgid "Bypass CN traffic"
msgstr "绕过中国流量"
@@ -342,7 +354,7 @@ msgstr "绕过中国流量"
msgid "Bypass mainland China"
msgstr "大陆白名单"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:191
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:195
msgid "Bypass mainland China traffic via firewall rules by default."
msgstr "默认使用防火墙规则绕过中国大陆流量。"
@@ -350,11 +362,11 @@ msgstr "默认使用防火墙规则绕过中国大陆流量。"
msgid "CA provider"
msgstr "CA 颁发机构"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:204
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:147
msgid "CN-IP DNS fallback (sing-box 1.14)"
msgstr "大陆 IP DNS 兜底(sing-box 1.14)"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:174
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:117
msgid "CNNIC Public DNS (210.2.4.8)"
msgstr "CNNIC 公共 DNS(210.2.4.8)"
@@ -375,27 +387,27 @@ msgstr "证书路径"
msgid "Check"
msgstr "检查"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:139
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:171
msgid "Check update"
msgstr "检查更新"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:167
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:108
msgid "China DNS server"
msgstr "国内 DNS 服务器"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:261
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:293
msgid "China IPv4 list version"
msgstr "国内 IPv4 库版本"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:265
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:297
msgid "China IPv6 list version"
msgstr "国内 IPv6 库版本"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:269
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:301
msgid "China list version"
msgstr "国内域名列表版本"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:125
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:126
msgid "Chromium / Cronet"
msgstr "Chromium / Cronet"
@@ -403,15 +415,15 @@ msgstr "Chromium / Cronet"
msgid "Cipher suites"
msgstr "密码套件"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:137
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:78
msgid "Cisco Public DNS (208.67.222.222)"
msgstr "思科公共 DNS(208.67.222.222)"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:231
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:263
msgid "Clean log"
msgstr "清空日志"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:123
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:124
msgid "Client"
msgstr "客户端"
@@ -423,7 +435,7 @@ msgstr "客户端设置"
msgid "Client version"
msgstr "客户端版本"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:136
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:77
msgid "CloudFlare Public DNS (1.1.1.1)"
msgstr "CloudFlare 公共 DNS(1.1.1.1)"
@@ -433,7 +445,7 @@ msgstr "Cloudflare"
#: htdocs/luci-static/resources/view/homeproxy-pro/client.js:130
#: htdocs/luci-static/resources/view/homeproxy-pro/server.js:146
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:212
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:244
msgid "Collecting data..."
msgstr "正在收集数据中..."
@@ -445,7 +457,7 @@ msgstr "仅常用端口(绕过 P2P 流量)"
msgid "Congestion control algorithm"
msgstr "拥塞控制算法"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:249
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:281
msgid "Connection check"
msgstr "连接检查"
@@ -477,8 +489,7 @@ msgstr "DNS 规则"
msgid "DNS Servers"
msgstr "DNS 服务器"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:56
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:122
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:59
msgid "DNS Settings"
msgstr "DNS 设置"
@@ -486,7 +497,7 @@ msgstr "DNS 设置"
msgid "DNS provider"
msgstr "DNS 提供商"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:39
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:42
msgid "DNS query timeout"
msgstr "DNS 查询超时"
@@ -494,7 +505,7 @@ msgstr "DNS 查询超时"
msgid "DNS rule"
msgstr "DNS 规则"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:410
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:425
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:221
msgid "DNS server"
msgstr "DNS 服务器"
@@ -507,49 +518,49 @@ msgstr "DNS01 验证"
msgid "DTLS"
msgstr "DTLS"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:179
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:211
msgid "Debug"
msgstr "调试"
#: htdocs/luci-static/resources/homeproxy-pro.js:62
#: htdocs/luci-static/resources/homeproxy-pro.js:334
#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:73
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:416
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:431
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:64
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:99
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:104
msgid "Default"
msgstr "默认"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:197
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:417
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:71
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:201
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:432
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:169
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:65
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:287
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:228
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:232
msgid "Default DNS (issued by WAN)"
msgstr "默认 DNS(由 WAN 下发)"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:66
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:164
msgid "Default DNS server"
msgstr "默认 DNS 服务器"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:223
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:227
msgid "Default DNS server for resolving domain name in the server address."
msgstr "用于解析服务器地址中的域名的默认 DNS 服务器。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:61
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:158
msgid "Default DNS strategy"
msgstr "默认 DNS 解析策略"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:203
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:207
msgid "Default outbound"
msgstr "默认出站"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:222
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:226
msgid "Default outbound DNS"
msgstr "默认出站 DNS"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:204
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:208
msgid "Default outbound for connections not matched by any routing rules."
msgstr "用于未被任何路由规则匹配的连接的默认出站。"
@@ -561,40 +572,39 @@ msgstr "默认包封装格式"
msgid "Default server name"
msgstr "默认服务器名称"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:40
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:43
msgid "Default timeout per DNS query in seconds (sing-box default: 10)."
msgstr "每次 DNS 查询的默认超时时间(秒,sing-box 默认为 10)。"
#: htdocs/luci-static/resources/homeproxy-pro.js:130
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:335
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:339
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:97
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:325
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:210
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:100
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:214
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:105
msgid "Direct"
msgstr "直连"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:165
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:166
msgid "Direct Domain List"
msgstr "直连域名列表"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:54
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:99
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:55
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:100
msgid "Direct IPv4 IP-s"
msgstr "直连 IPv4 地址"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:57
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:102
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:58
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:103
msgid "Direct IPv6 IP-s"
msgstr "直连 IPv6 地址"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:60
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:61
msgid "Direct MAC-s"
msgstr "直连 MAC 地址"
#: htdocs/luci-static/resources/homeproxy-pro.js:257
#: htdocs/luci-static/resources/homeproxy-pro.js:270
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:48
#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:26
#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:54
#: htdocs/luci-static/resources/view/homeproxy-pro/node.js:93
@@ -603,7 +613,7 @@ msgstr "直连 MAC 地址"
msgid "Disable"
msgstr "禁用"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:209
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:213
msgid "Disable (the service)"
msgstr "禁用(服务)"
@@ -620,12 +630,12 @@ msgstr ""
"禁用自 sing-box 1.14 起默认开启的 Chrome QUIC 握手伪装。仅在服务器证书为 "
"Ed25519 或握手失败时才需要开启。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:448
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:83
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:463
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:182
msgid "Disable DNS cache"
msgstr "禁用 DNS 缓存"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:449
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:464
msgid "Disable DNS cache in this query."
msgstr "在本次查询中禁用 DNS 缓存。"
@@ -637,32 +647,32 @@ msgstr "禁用 HTTP 验证"
msgid "Disable TLS ALPN challenge"
msgstr "禁用 TLS ALPN 认证"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:359
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:365
msgid "Disable UDP domain unmapping"
msgstr "禁用 UDP 域名映射"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:212
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:216
msgid "Disable cache and save cache in this query."
msgstr "在本次查询中禁用缓存。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:85
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:184
msgid "Disable cache expire"
msgstr "缓存永不过期"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:211
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:215
msgid "Disable dns cache"
msgstr "禁用 DNS 缓存"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:251
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:255
msgid "Disable optimistic DNS caching for this query."
msgstr "对此查询禁用乐观 DNS 缓存。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:467
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:482
msgid "Disable optimistic DNS caching in this lookup (1.14)."
msgstr "关闭本次查询解析的乐观 DNS 缓存(1.14)。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:250
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:466
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:254
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:481
msgid "Disable optimistic cache"
msgstr "禁用乐观缓存"
@@ -670,15 +680,15 @@ msgstr "禁用乐观缓存"
msgid "Disabled"
msgstr "禁用"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:487
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:502
msgid "Domain keyword"
msgstr "域名关键词"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:478
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:493
msgid "Domain name"
msgstr "域名"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:491
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:506
msgid "Domain regex"
msgstr "域名正则表达式"
@@ -687,15 +697,15 @@ msgid "Domain resolver"
msgstr "域名解析器"
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:77
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:198
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:202
msgid "Domain strategy"
msgstr "域名解析策略"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:442
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:457
msgid "Domain strategy for resolving the domain names."
msgstr "目标域名的解析策略。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:483
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:498
msgid "Domain suffix"
msgstr "域名后缀"
@@ -703,11 +713,11 @@ msgstr "域名后缀"
msgid "Domains"
msgstr "域名"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:435
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:450
msgid "Don't drop packets"
msgstr "不丢弃数据包"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:296
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:300
msgid "Don't drop requests"
msgstr "不丢弃请求"
@@ -719,13 +729,13 @@ msgstr "下载带宽"
msgid "Download bandwidth in Mbps."
msgstr "下载带宽(单位:Mbps)。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:431
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:437
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:446
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:452
msgid "Drop packets"
msgstr "丢弃数据包"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:291
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:298
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:295
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:302
msgid "Drop requests"
msgstr "丢弃请求"
@@ -778,9 +788,9 @@ msgstr "ECH 配置路径"
msgid "ECH key"
msgstr "ECH 密钥"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:222
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:459
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:88
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:226
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:474
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:187
msgid "EDNS Client subnet"
msgstr "EDNS 客户端子网"
@@ -851,11 +861,11 @@ msgstr "启用 UDP 分片。"
msgid "Enable connection reuse (the Snell v2 CONNECT command)."
msgstr "启用连接复用(Snell v2 CONNECT 指令)。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:175
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:179
msgid "Enable endpoint-independent NAT"
msgstr "启用端点独立 NAT"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:195
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:199
msgid ""
"Enable neighbor resolution so rules can match LAN devices by hostname (1.14)."
msgstr "启用邻居解析,使规则可按主机名匹配局域网设备(1.14)。"
@@ -884,15 +894,15 @@ msgstr "加密方式"
msgid "Endpoint independent"
msgstr "端点无关"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:182
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:214
msgid "Error"
msgstr "错误"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:181
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:185
msgid "Evaluate (1.14)"
msgstr "Evaluate(1.14)"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:234
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:238
msgid "Evaluate tag"
msgstr "Evaluate 标签"
@@ -901,28 +911,30 @@ msgstr "Evaluate 标签"
#: htdocs/luci-static/resources/homeproxy-pro.js:1093
#: htdocs/luci-static/resources/homeproxy-pro.js:1106
#: htdocs/luci-static/resources/homeproxy-pro.js:1125
-#: htdocs/luci-static/resources/homeproxy-pro.js:1134
-#: htdocs/luci-static/resources/homeproxy-pro.js:1145
+#: htdocs/luci-static/resources/homeproxy-pro.js:1140
#: htdocs/luci-static/resources/homeproxy-pro.js:1154
-#: htdocs/luci-static/resources/homeproxy-pro.js:1156
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:158
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:203
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:145
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:157
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:161
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:183
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:194
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:198
+#: htdocs/luci-static/resources/homeproxy-pro.js:1163
+#: htdocs/luci-static/resources/homeproxy-pro.js:1174
+#: htdocs/luci-static/resources/homeproxy-pro.js:1183
+#: htdocs/luci-static/resources/homeproxy-pro.js:1185
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:159
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:204
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:86
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:98
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:102
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:126
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:137
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:141
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:141
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:155
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:158
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:112
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:72
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:75
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:116
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:77
#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:80
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:83
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:123
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:126
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:85
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:88
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:128
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:131
#: htdocs/luci-static/resources/view/homeproxy-pro/node.js:484
#: htdocs/luci-static/resources/view/homeproxy-pro/node.js:741
#: htdocs/luci-static/resources/view/homeproxy-pro/node.js:744
@@ -943,17 +955,17 @@ msgstr "外部账户 MAC 密钥"
msgid "External account key ID"
msgstr "外部账户密钥标识符"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:324
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:328
msgid "Extra records"
msgstr "附加记录"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:117
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:122
msgid ""
"Extra rule-set tags sharing these options. Requires {tag} in path/url (1.14)."
msgstr ""
"共享这些选项的额外规则集标签。使用多标签时 path/url 需包含 {tag}(1.14)。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:116
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:121
msgid "Extra tags"
msgstr "额外标签"
@@ -965,8 +977,8 @@ msgstr "关闭导入对话框失败:%s"
msgid "Failed to generate %s, error: %s."
msgstr "生成 %s 失败,错误:%s。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:124
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:177
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:125
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:178
msgid "Failed to read domain list: %s."
msgstr "读取域名列表失败:%s。"
@@ -974,7 +986,7 @@ msgstr "读取域名列表失败:%s。"
msgid "Failed to upload %s, error: %s."
msgstr "上传 %s 失败,错误:%s。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:183
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:215
msgid "Fatal"
msgstr "致命"
@@ -986,11 +998,11 @@ msgstr "过滤关键词"
msgid "Filter nodes"
msgstr "过滤节点"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:194
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:198
msgid "Find neighbor hosts"
msgstr "发现邻居主机"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:126
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:127
msgid "Firefox / uquic firefox"
msgstr "Firefox / uquic firefox"
@@ -1011,16 +1023,16 @@ msgstr "用于解析服务器地址中的域名。"
msgid "Format"
msgstr "格式"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:376
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:385
msgid "Fragment TLS handshake into multiple TLS records."
msgstr "将 TLS 握手分片为多个 TLS 记录"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:381
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:392
msgid ""
"Fragment TLS handshakes. Due to poor performance, try %s first."
msgstr "分片 TLS 握手。由于性能较差,请先尝试 %s。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:386
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:399
msgid "Fragment fallback delay"
msgstr "分片回退延迟"
@@ -1028,7 +1040,11 @@ msgstr "分片回退延迟"
msgid "GET"
msgstr "GET"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:273
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:309
+msgid "GFW list addresses"
+msgstr "GFW 列表地址数"
+
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:305
msgid "GFW list version"
msgstr "GFW 域名列表版本"
@@ -1036,15 +1052,15 @@ msgstr "GFW 域名列表版本"
msgid "GFWList"
msgstr "GFWList"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:72
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:73
msgid "Gaming mode IPv4 IP-s"
msgstr "游戏模式 IPv4 地址"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:74
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:75
msgid "Gaming mode IPv6 IP-s"
msgstr "游戏模式 IPv6 地址"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:77
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:78
msgid "Gaming mode MAC-s"
msgstr "游戏模式 MAC 地址"
@@ -1058,7 +1074,7 @@ msgstr "Gecko(1.14)"
msgid "Generate"
msgstr "生成"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:277
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:312
msgid "GitHub token"
msgstr "GitHub 令牌"
@@ -1070,15 +1086,15 @@ msgstr "全局"
msgid "Global padding"
msgstr "全局填充"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:79
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:80
msgid "Global proxy IPv4 IP-s"
msgstr "全局代理 IPv4 地址"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:82
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:83
msgid "Global proxy IPv6 IP-s"
msgstr "全局代理 IPv6 地址"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:85
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:86
msgid "Global proxy MAC-s"
msgstr "全局代理 MAC 地址"
@@ -1086,11 +1102,11 @@ msgstr "全局代理 MAC 地址"
msgid "Global settings"
msgstr "全局设置"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:255
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:287
msgid "Google"
msgstr "谷歌"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:138
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:79
msgid "Google Public DNS (8.8.8.8)"
msgstr "谷歌公共 DNS(8.8.8.8)"
@@ -1162,7 +1178,7 @@ msgstr "劫持"
#: htdocs/luci-static/resources/view/homeproxy-pro/client.js:70
#: htdocs/luci-static/resources/view/homeproxy-pro/client.js:119
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:299
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:334
#: root/usr/share/luci/menu.d/luci-app-homeproxy-pro.json:3
msgid "HomeProxy"
msgstr "HomeProxy"
@@ -1204,7 +1220,7 @@ msgid ""
"command (in seconds)."
msgstr "服务器等待客户端发送认证命令的时间(单位:秒)。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:400
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:414
msgid "How the forged segment is rejected by the real server."
msgstr "伪造段被真实服务器拒绝的方式。"
@@ -1216,7 +1232,7 @@ msgstr "Hysteria"
msgid "Hysteria2"
msgstr "Hysteria2"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:503
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:519
msgid "IP CIDR"
msgstr "IP CIDR"
@@ -1240,7 +1256,7 @@ msgstr "IPv6"
msgid "IPv6 only"
msgstr "仅 IPv6"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:135
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:139
msgid "IPv6 support"
msgstr "IPv6 支持"
@@ -1257,13 +1273,13 @@ msgstr "空闲会话检查超时"
msgid "Idle timeout"
msgstr "空闲超时"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:365
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:372
msgid ""
"If enabled, attempts to connect UDP connection to the destination instead of "
"listen."
msgstr "如果启用,尝试主动连接到目标的 UDP 而不是监听。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:360
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:366
msgid ""
"If enabled, for UDP proxy requests addressed to a domain, the original "
"packet address will be sent in the response instead of the mapped domain."
@@ -1277,7 +1293,7 @@ msgid ""
"connections."
msgstr "如果启用,客户端传输即使没有活动连接也会发送 keepalive ping。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:199
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:203
msgid ""
"If set, the requested domain name will be resolved to IP before routing."
msgstr "如果设置,请求的域名将在路由前被解析为 IP 地址。"
@@ -1317,7 +1333,7 @@ msgid ""
"manually."
msgstr "节点已导入,但对话框未能自动关闭。请手动关闭。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:183
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:187
#: htdocs/luci-static/resources/view/homeproxy-pro/server.js:615
msgid "In seconds."
msgstr "单位:秒。"
@@ -1337,15 +1353,15 @@ msgid ""
"seconds."
msgstr "在检查中,关闭空闲时间超过此值的会话(单位:秒)。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:180
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:212
msgid "Info"
msgstr "信息"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:110
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:115
msgid "Initial path"
msgstr "初始路径"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:394
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:407
msgid ""
"Inject a forged TLS ClientHello carrying this SNI before the real one to "
"fool SNI-filtering middleboxes. Requires elevated privileges."
@@ -1375,15 +1391,15 @@ msgid ""
"seconds)."
msgstr "发送心跳包以保持连接存活的时间间隔(单位:秒)。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:160
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:161
msgid "Invert"
msgstr "反转"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:161
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:162
msgid "Invert match result."
msgstr "反转匹配结果"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:240
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:244
msgid ""
"Judge this response-dependent rule in parallel; first match wins (1.14)."
msgstr "并行判定此响应依赖规则,首个命中即生效(1.14)。"
@@ -1405,6 +1421,14 @@ msgstr "LAN IP 策略"
msgid "Label"
msgstr "标签"
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:56
+msgid ""
+"Leave empty to infer it from a .json or .srs path/"
+"url; sing-box requires it for any other extension."
+msgstr ""
+"留空则按 .json 或 .srs 路径/URL 推断格式;其他扩展"
+"名必须显式指定。"
+
#: htdocs/luci-static/resources/view/homeproxy-pro/node.js:258
#: htdocs/luci-static/resources/view/homeproxy-pro/server.js:305
msgid ""
@@ -1414,7 +1438,7 @@ msgstr ""
"仅为兼容目的提供旧协议(VMess MD5 认证)支持,不建议使用大于 1 的 alterId。提"
"供旧协议支持(VMess MD5 身份验证)仅出于兼容性目的,不建议使用 alterId > 1。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:172
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:176
msgid "Less compatibility and sometimes better performance."
msgstr "有时性能更好。"
@@ -1437,15 +1461,15 @@ msgstr "要测试的节点列表。"
msgid "List of supported application level protocols, in order of preference."
msgstr "支持的应用层协议协商列表,按顺序排列。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:315
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:319
msgid "List of text DNS record to respond as answers."
msgstr "要响应的 DNS 记录列表。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:325
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:329
msgid "List of text DNS record to respond as extra records."
msgstr "要响应的附加 DNS 记录列表。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:320
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:324
msgid "List of text DNS record to respond as name servers."
msgstr "要响应的域名服务器(NS) DNS 记录列表。"
@@ -1461,7 +1485,7 @@ msgstr "监听接口"
msgid "Listen port"
msgstr "监听端口"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:210
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:242
msgid "Loading"
msgstr "加载中"
@@ -1473,7 +1497,7 @@ msgstr "本地"
msgid "Local address"
msgstr "本地地址"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:111
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:116
msgid ""
"Local file with initial rule-set content; avoids blocking startup on first "
"download (1.14)."
@@ -1499,11 +1523,11 @@ msgstr "主 UDP 节点"
msgid "Main node"
msgstr "主节点"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:157
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:158
msgid "Make IP CIDR in rule set used to match the source IP."
msgstr "使规则集中的 IP CIDR 用于匹配源 IP。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:157
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:158
msgid "Make IP CIDR in rule sets match the source IP."
msgstr "使规则集中的 IP CIDR 匹配源 IP。"
@@ -1511,79 +1535,79 @@ msgstr "使规则集中的 IP CIDR 匹配源 IP。"
msgid "Masquerade"
msgstr "伪装"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:528
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:544
msgid "Match DNSSEC OK"
msgstr "匹配 DNSSEC OK"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:504
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:520
msgid ""
"Match IP CIDR with the evaluated response (needs match_response; old rules "
"are auto-wrapped in 1.14)."
msgstr ""
"以已评估的响应匹配 IP CIDR(需要 match_response;旧规则在 1.14 自动包装)。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:505
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:521
msgid "Match IP CIDR."
msgstr "匹配 IP CIDR。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:514
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:530
msgid "Match LAN device by MAC address."
msgstr "按 MAC 地址匹配局域网设备。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:519
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:535
msgid ""
"Match LAN device hostname via neighbor resolution; enable find_neighbor."
msgstr "通过邻居解析按主机名匹配局域网设备;需启用 find_neighbor。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:484
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:499
msgid "Match domain suffix."
msgstr "匹配域名后缀。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:488
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:503
msgid "Match domain using keyword."
msgstr "使用关键词匹配域名。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:492
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:507
msgid "Match domain using regular expression."
msgstr "使用正则表达式匹配域名。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:479
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:494
msgid "Match full domain."
msgstr "匹配完整域名。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:549
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:565
msgid "Match port range. Format as START:/:END/START:END."
msgstr "匹配端口范围。格式为 START:/:END/START:END。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:544
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:560
msgid "Match port."
msgstr "匹配端口。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:509
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:525
msgid "Match private IP"
msgstr "匹配私有 IP"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:510
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:526
msgid "Match private IP with the evaluated response (needs match_response)."
msgstr "以已评估的响应匹配私网 IP(需要 match_response)。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:500
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:516
msgid "Match private source IP"
msgstr "匹配私有源 IP"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:554
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:570
msgid "Match process name."
msgstr "匹配进程名。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:562
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:578
msgid "Match process path using regular expression."
msgstr "使用正则表达式匹配进程路径。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:558
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:574
msgid "Match process path."
msgstr "匹配进程路径。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:529
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:545
msgid "Match queries with the DNSSEC OK bit set (1.14)."
msgstr "匹配设置了 DNSSEC OK 位的查询(1.14)。"
@@ -1591,11 +1615,11 @@ msgstr "匹配设置了 DNSSEC OK 位的查询(1.14)。"
msgid "Match query type."
msgstr "匹配请求类型。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:229
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:233
msgid "Match response"
msgstr "匹配响应"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:141
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:142
msgid ""
"Match rule set. DNS: rules referencing rule-sets that contain query_type are "
"incompatible with legacy address filters (ip_cidr / ip_is_private) — use "
@@ -1604,7 +1628,7 @@ msgstr ""
"匹配规则集。DNS:引用含 query_type 规则集的规则与 legacy 地址过滤(ip_cidr / "
"ip_is_private)不兼容——请改用 match_response(1.14)。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:142
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:143
msgid ""
"Match rule set. Since 1.14: a rule-set holding a single default rule is "
"merged into this rule; any other rule-set matches as an OR collection."
@@ -1612,27 +1636,27 @@ msgstr ""
"匹配规则集。自 1.14:仅含单条 default 规则的规则集字段会合并进本条规则;其余"
"规则集整体按 OR 集合匹配。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:496
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:512
msgid "Match source IP CIDR."
msgstr "匹配源 IP CIDR。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:539
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:555
msgid "Match source port range. Format as START:/:END/START:END."
msgstr "匹配源端口范围。格式为 START:/:END/START:END。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:534
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:550
msgid "Match source port."
msgstr "匹配源端口。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:524
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:540
msgid "Match the EDNS Client Subnet in the query (1.14)."
msgstr "匹配查询中的 EDNS Client Subnet(1.14)。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:270
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:274
msgid "Match the evaluated response code (requires match_response)."
msgstr "匹配已评估响应的响应码(需 match_response)。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:137
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:138
msgid "Match user name."
msgstr "匹配用户名。"
@@ -1652,7 +1676,7 @@ msgstr "最大跳变间隔(1.14)"
msgid "Max obfs packet size (1.14)"
msgstr "最大混淆包长(1.14)"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:37
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:40
msgid "Max time an expired entry may be served. Examples: 3d, 1h."
msgstr "过期缓存项可被乐观返回的最长时间。示例:3d、1h。"
@@ -1700,10 +1724,10 @@ msgid "Maximum streams"
msgstr "最大流数量"
#: htdocs/luci-static/resources/homeproxy-pro.js:581
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:289
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:298
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:429
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:437
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:293
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:302
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:444
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:452
msgid "Method"
msgstr "方式"
@@ -1734,11 +1758,11 @@ msgid "Minimum streams"
msgstr "最小流数量"
#: htdocs/luci-static/resources/homeproxy-pro.js:136
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:157
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:161
msgid "Mixed"
msgstr "混合"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:168
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:172
msgid "Mixed system TCP stack and gVisor UDP stack."
msgstr "混合系统 TCP 栈和 gVisor UDP 栈。"
@@ -1767,7 +1791,7 @@ msgstr "未运行"
msgid "NOTE: Save current settings before updating subscriptions."
msgstr "注意:更新订阅前先保存当前配置。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:319
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:323
msgid "NS"
msgstr "NS"
@@ -1797,6 +1821,10 @@ msgstr "无 TCP 传输层, 纯 HTTP 已合并到 HTTP 传输层。"
msgid "No additional encryption support: It's basically duplicate encryption."
msgstr "无额外加密支持:它基本上是重复加密。"
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:49
+msgid "No filter (proxy every client)"
+msgstr "不过滤(代理全部客户端)"
+
#: htdocs/luci-static/resources/view/homeproxy-pro/node.js:797
msgid "No subscription available"
msgstr "无可用订阅"
@@ -1846,8 +1874,8 @@ msgstr "混淆主机"
msgid "Obfuscation mode"
msgstr "混淆模式"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:114
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:171
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:115
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:172
msgid ""
"One domain per line. No spaces, no comments, no wildcards; the backend "
"rejects invalid characters and tells you which line."
@@ -1861,15 +1889,15 @@ msgstr "只处理来自指定接口的流量。留空表示全部。"
msgid "Only proxy mainland China"
msgstr "仅代理中国大陆"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:30
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:33
msgid "Optimistic DNS cache"
msgstr "乐观 DNS 缓存"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:36
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:39
msgid "Optimistic cache timeout"
msgstr "乐观缓存超时"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:235
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:239
msgid "Optional tag for this evaluate result."
msgstr "此 evaluate 结果的可选标签。"
@@ -1881,10 +1909,10 @@ msgstr "可选;仅在连接多用户 Snell 服务器时需要。"
msgid "Other fields"
msgstr "其他字段"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:329
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:333
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:91
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:319
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:93
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:98
msgid "Outbound"
msgstr "出站"
@@ -1892,36 +1920,36 @@ msgstr "出站"
msgid "Outbound node"
msgstr "出站节点"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:94
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:99
msgid ""
"Outbound used to download this rule-set (via sing-box 1.14 http_clients)."
msgstr "下载此规则集所使用的出站(经 sing-box 1.14 http_clients 生效)。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:347
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:351
msgid "Override address"
msgstr "覆盖地址"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:472
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:487
msgid "Override dns.timeout for this lookup, in seconds (1.14)."
msgstr "覆盖本次查询解析的 dns.timeout(秒)(1.14)。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:257
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:261
msgid "Override dns.timeout for this query, in seconds."
msgstr "覆盖本次查询的 dns.timeout,单位秒。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:353
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:358
msgid "Override port"
msgstr "覆盖端口"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:348
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:352
msgid "Override the connection destination address."
msgstr "覆盖目标连接地址。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:354
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:359
msgid "Override the connection destination port."
msgstr "覆盖目标连接端口。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:131
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:72
msgid "Overseas DNS server"
msgstr "国外 DNS 服务器"
@@ -1941,7 +1969,7 @@ msgstr "数据包编码"
msgid "Padding scheme"
msgstr "填充方案"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:184
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:216
msgid "Panic"
msgstr "崩溃"
@@ -1953,7 +1981,7 @@ msgstr "密码"
#: htdocs/luci-static/resources/homeproxy-pro.js:576
#: htdocs/luci-static/resources/homeproxy-pro.js:610
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:259
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:61
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:66
msgid "Path"
msgstr "路径"
@@ -1961,13 +1989,13 @@ msgstr "路径"
msgid "Peer pubkic key"
msgstr "对端公钥"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:176
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:180
msgid ""
"Performance may degrade slightly, so it is not recommended to enable on when "
"it is not needed."
msgstr "性能可能会略有下降,建议仅在需要时开启。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:44
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:47
msgid "Persist DNS cache across restarts (sing-box 1.14, replaces Store RDRC)."
msgstr "重启后持久化 DNS 缓存(sing-box 1.14,替代 Store RDRC)。"
@@ -1987,13 +2015,13 @@ msgstr "插件"
msgid "Plugin opts"
msgstr "插件参数"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:543
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:559
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:254
#: htdocs/luci-static/resources/view/homeproxy-pro/node.js:63
msgid "Port"
msgstr "端口"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:114
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:118
msgid "Port %s alrealy exists!"
msgstr "端口 %s 已存在!"
@@ -2005,7 +2033,7 @@ msgstr "端口字段"
msgid "Port hopping interval in seconds."
msgstr "端口跳跃间隔(单位:秒)。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:548
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:564
msgid "Port range"
msgstr "端口范围"
@@ -2013,7 +2041,7 @@ msgstr "端口范围"
msgid "Pre-shared key"
msgstr "预共享密钥"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:180
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:184
msgid "Predefined"
msgstr "预定义"
@@ -2042,15 +2070,15 @@ msgstr "新证书的私钥类型(1.14)。留空使用默认值。"
msgid "Process fields"
msgstr "进程字段"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:553
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:569
msgid "Process name"
msgstr "进程名"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:557
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:573
msgid "Process path"
msgstr "进程路径"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:561
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:577
msgid "Process path (regex)"
msgstr "进程路径(正则表达式)"
@@ -2071,25 +2099,25 @@ msgid ""
"default in v2ray and cannot be disabled)."
msgstr "协议参数。 如启用会随机浪费流量(在 v2ray 中默认启用并且无法禁用)。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:108
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:109
msgid "Proxy Domain List"
msgstr "代理域名列表"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:63
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:92
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:64
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:93
msgid "Proxy IPv4 IP-s"
msgstr "代理 IPv4 地址"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:66
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:95
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:67
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:96
msgid "Proxy IPv6 IP-s"
msgstr "代理 IPv6 地址"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:69
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:70
msgid "Proxy MAC-s"
msgstr "代理 MAC 地址"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:50
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:51
msgid "Proxy all except listed"
msgstr "仅允许列表外"
@@ -2097,11 +2125,11 @@ msgstr "仅允许列表外"
msgid "Proxy filter mode"
msgstr "代理过滤模式"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:49
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:50
msgid "Proxy listed only"
msgstr "仅允许列表内"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:122
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:126
msgid "Proxy mode"
msgstr "代理模式"
@@ -2120,12 +2148,12 @@ msgstr "QUIC"
msgid "QUIC congestion control algorithm."
msgstr "QUIC 拥塞控制算法。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:523
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:539
msgid "Query EDNS client subnet"
msgstr "查询 EDNS Client Subnet"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:256
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:471
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:260
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:486
msgid "Query timeout"
msgstr "查询超时"
@@ -2133,7 +2161,7 @@ msgstr "查询超时"
msgid "Query type"
msgstr "请求类型"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:302
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:306
msgid "RCode"
msgstr "RCode"
@@ -2164,7 +2192,7 @@ msgstr "REALITY 标识符"
msgid "RUNNING"
msgstr "运行中"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:239
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:243
msgid "Race"
msgstr "并行竞争"
@@ -2180,19 +2208,19 @@ msgstr "检测到递归出站!"
msgid "Recursive resolver detected!"
msgstr "检测到递归解析器!"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:123
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:127
msgid "Redirect TCP"
msgstr "Redirect TCP"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:125
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:129
msgid "Redirect TCP + TProxy UDP"
msgstr "Redirect TCP + TProxy UDP"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:127
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:131
msgid "Redirect TCP + Tun UDP"
msgstr "Redirect TCP + Tun UDP"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:236
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:268
msgid "Refresh every %s seconds."
msgstr "每 %s 秒刷新。"
@@ -2200,7 +2228,7 @@ msgstr "每 %s 秒刷新。"
msgid "Region ID"
msgstr "区域 ID"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:178
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:182
msgid "Reject"
msgstr "拒绝"
@@ -2212,7 +2240,7 @@ msgstr "远程"
msgid "Remove %s nodes"
msgstr "移除 %s 个节点"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:263
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:267
msgid "Remove EDNS client subnet"
msgstr "移除 EDNS Client Subnet"
@@ -2220,15 +2248,15 @@ msgstr "移除 EDNS Client Subnet"
msgid "Remove all nodes from subscriptions"
msgstr "移除所有订阅节点"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:264
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:268
msgid "Remove the edns0-subnet record from the query (1.14)."
msgstr "从查询中移除 edns0-subnet 记录(1.14)。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:290
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:294
msgid "Reply with REFUSED"
msgstr "回复 REFUSED"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:430
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:445
msgid "Reply with TCP RST / ICMP port unreachable"
msgstr "回复 TCP RST / ICMP 端口不可达"
@@ -2236,15 +2264,15 @@ msgstr "回复 TCP RST / ICMP 端口不可达"
msgid "Reserved field bytes"
msgstr "保留字段字节"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:184
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:188
msgid "Resolve"
msgstr "解析"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:441
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:456
msgid "Resolve strategy"
msgstr "解析策略"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:132
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:73
msgid ""
"Resolves every domain that is not on the China DNS path, through the "
"selected proxy node. A domestic resolver answers these queries with polluted "
@@ -2256,38 +2284,37 @@ msgstr ""
"回被污染或 NXDOMAIN 的结果,这正是代理本身正常、境外网站却解析失败的原因,因"
"此这里只提供境外服务。支持 UDP、TCP、DoH、DoQ、DoT,未指定协议时使用 TCP。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:168
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:109
msgid ""
"Resolves the domains on the China path, so it has to be a domestic service "
"to get the correct local CDN answers. Keep the default; the upstream may "
"block UDP/53, in which case a DoH address (https://dns.alidns.com/dns-query, "
-"https://doh.pub/dns-query) also works. Support UDP, TCP, DoH, DoQ, DoT."
-msgstr ""
-"解析中国路径上的域名,必须是国内服务才能获得正确的本地 CDN 结果。建议保持默认"
-"值;若上游封禁 UDP/53,也可使用 DoH 地址(https://dns.alidns.com/dns-query、"
-"https://doh.pub/dns-query)。支持 UDP、TCP、DoH、DoQ、DoT。"
+"https://doh.pub/dns-query) also works. Support UDP, TCP, DoH, DoQ, DoT. A "
+"DoH/DoT address here also sends the bootstrap lookup back to the WAN "
+"resolver: only a bare IP can resolve the proxy DNS endpoint without the ISP."
+msgstr "解析走 China 路径的域名,因此必须是国内服务才能拿到正确的本地 CDN 结果。保持默认值;上游可能屏蔽 UDP/53,此时 DoH 地址(https://dns.alidns.com/dns-query、https://doh.pub/dns-query)也可用。支持 UDP、TCP、DoH、DoQ、DoT。填 DoH/DoT 地址还会让引导解析回退到 WAN 解析器:只有裸 IP 才能在不依赖 ISP 的情况下解析出代理 DNS 端点。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:258
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:290
msgid "Resources management"
msgstr "资源管理"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:281
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:285
msgid "Response NS"
msgstr "响应 NS 记录"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:269
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:273
msgid "Response RCode"
msgstr "响应 RCode"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:276
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:280
msgid "Response answer"
msgstr "响应 Answer 记录"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:285
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:289
msgid "Response extra"
msgstr "响应 Extra 记录"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:31
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:34
msgid ""
"Return expired cache immediately and refresh in background (sing-box 1.14)."
msgstr "立即返回已过期缓存并在后台刷新(sing-box 1.14)。"
@@ -2300,21 +2327,21 @@ msgstr "复用地址"
msgid "Reuse listener address."
msgstr "复用监听地址。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:216
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:453
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:220
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:468
msgid "Rewrite TTL"
msgstr "重写 TTL"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:217
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:454
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:221
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:469
msgid "Rewrite TTL in DNS responses."
msgstr "在 DNS 响应中重写 TTL。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:177
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:181
msgid "Route"
msgstr "路由"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:183
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:187
msgid "Route options"
msgstr "路由选项"
@@ -2350,16 +2377,16 @@ msgstr "路由规则"
msgid "Rule Set"
msgstr "规则集"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:140
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:141
#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:31
msgid "Rule set"
msgstr "规则集"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:156
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:157
msgid "Rule set IP CIDR as source IP"
msgstr "规则集 IP CIDR 作为源 IP"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:68
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:73
msgid "Rule set URL"
msgstr "规则集 URL"
@@ -2380,7 +2407,7 @@ msgstr "STUN"
msgid "SUoT version"
msgstr "SUoT 版本"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:128
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:129
msgid "Safari / Apple Network API"
msgstr "Safari / Apple Network API"
@@ -2392,8 +2419,8 @@ msgstr "Salamander"
msgid "Same as main node"
msgstr "保持与主节点一致"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:284
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:290
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:319
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:325
msgid "Save"
msgstr "保存"
@@ -2405,11 +2432,11 @@ msgstr "保存当前设置"
msgid "Save subscriptions settings"
msgstr "保存订阅设置"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:245
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:249
msgid "Send the query before pending race rules are judged (1.14)."
msgstr "在待定 race 规则判定前即发送查询(1.14)。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:191
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:195
#: htdocs/luci-static/resources/view/homeproxy-pro/server.js:176
msgid "Server"
msgstr "服务器"
@@ -2464,7 +2491,7 @@ msgstr "Snell(1.14)"
msgid "Snell version"
msgstr "Snell 版本"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:124
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:125
msgid "Sniffed client type (QUIC client type or SSH client name)."
msgstr "嗅探到的客户端类型(QUIC 客户端类型或 SSH 客户端名称)。"
@@ -2476,7 +2503,7 @@ msgstr ""
"嗅探到的协议,详情请参阅 嗅探。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:143
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:147
msgid "Sniffer: advanced mode (100ms, universal list)"
msgstr "嗅探:进阶模式(100ms,全协议列表)"
@@ -2500,31 +2527,31 @@ msgstr "Socks4A"
msgid "Socks5"
msgstr "Socks5"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:495
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:511
msgid "Source IP CIDR"
msgstr "源 IP CIDR"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:513
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:529
msgid "Source MAC address"
msgstr "来源 MAC 地址"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:57
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:59
msgid "Source file"
msgstr "源文件"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:518
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:534
msgid "Source hostname"
msgstr "来源主机名"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:533
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:549
msgid "Source port"
msgstr "源端口"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:538
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:554
msgid "Source port range"
msgstr "源端口范围"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:411
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:426
msgid ""
"Specifies DNS server tag to use instead of selecting through DNS routing."
msgstr "指定使用的 DNS 服务器,而不是通过 DNS 规则选择。"
@@ -2567,7 +2594,7 @@ msgid ""
"commas."
msgstr "指定需要被代理的目标端口。多个端口必须用逗号隔开。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:244
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:248
msgid "Speculative"
msgstr "抢先查询"
@@ -2575,7 +2602,7 @@ msgstr "抢先查询"
msgid "Standard (default)"
msgstr "标准(默认)"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:43
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:46
msgid "Store DNS cache"
msgstr "存储 DNS 缓存"
@@ -2615,7 +2642,7 @@ msgstr "订阅"
msgid "Successfully imported %s nodes of total %s."
msgstr "成功导入 %s 个节点,共 %s 个。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:120
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:152
msgid "Successfully updated."
msgstr "更新成功。"
@@ -2628,16 +2655,16 @@ msgstr ""
"支持 Hysteria、Shadowsocks、Trojan、v2rayN(VMess)和 XTLS(VLESS)在线配置交"
"付标准。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:160
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:164
msgid "System"
msgstr "系统"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:198
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:418
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:72
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:202
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:433
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:170
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:66
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:288
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:229
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:233
msgid "System DNS"
msgstr "系统 DNS"
@@ -2652,11 +2679,11 @@ msgstr "TCP"
msgid "TCP fast open"
msgstr "TCP 快速打开"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:154
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:158
msgid "TCP/IP stack"
msgstr "TCP/IP 协议栈"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:155
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:159
msgid "TCP/IP stack."
msgstr "TCP/IP 协议栈。"
@@ -2675,7 +2702,7 @@ msgstr "TLS ALPN"
msgid "TLS SNI"
msgstr "TLS SNI"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:380
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:391
msgid "TLS fragment"
msgstr "TLS 分片"
@@ -2687,16 +2714,16 @@ msgstr "TLS 握手超时(秒)。默认 15 秒。"
msgid "TLS is not enforced. If TLS is not configured, plain HTTP 1.1 is used."
msgstr "不强制执行 TLS。如未配置 TLS,将使用纯 HTTP 1.1。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:375
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:382
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:384
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:393
msgid "TLS record fragment"
msgstr "TLS 记录分片"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:393
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:406
msgid "TLS spoof SNI (1.14)"
msgstr "TLS spoof SNI(1.14)"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:399
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:413
msgid "TLS spoof method (1.14)"
msgstr "TLS spoof 方式(1.14)"
@@ -2724,11 +2751,11 @@ msgstr ""
msgid "Tag of an outbound for connecting to the dns server."
msgstr "用于连接到 DNS 服务器的出站标签。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:192
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:196
msgid "Tag of the target dns server."
msgstr "目标 DNS 服务器标签。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:330
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:334
msgid "Tag of the target outbound."
msgstr "目标出站标签。"
@@ -2737,7 +2764,7 @@ msgid ""
"Tell the client to use the BBR flow control algorithm instead of Hysteria CC."
msgstr "让客户端使用 BBR 流控算法。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:175
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:118
msgid "Tencent Public DNS (119.29.29.29)"
msgstr "腾讯公共 DNS(119.29.29.29)"
@@ -2761,7 +2788,7 @@ msgstr "测试间隔时间必须小于或等于空闲超时时间。"
msgid "Test tolerance"
msgstr "测试容差"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:277
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:281
msgid "Text DNS records to match in the evaluated answer."
msgstr "用于匹配已评估 Answer 的文本 DNS 记录。"
@@ -2769,7 +2796,7 @@ msgstr "用于匹配已评估 Answer 的文本 DNS 记录。"
msgid "The ACME CA provider to use."
msgstr "使用的 ACME CA 颁发机构。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:62
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:159
msgid "The DNS strategy for resolving the domain name in the address."
msgstr "解析域名的默认策略。"
@@ -2847,7 +2874,7 @@ msgid ""
"account."
msgstr "创建或选择现有 ACME 服务器帐户时使用的电子邮件地址。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:387
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:400
msgid ""
"The fallback value in milliseconds used when TLS segmentation cannot "
"automatically determine the wait time."
@@ -2909,7 +2936,7 @@ msgstr "v6 下,上方的预共享密钥(密码)长度须为 12-255 字节
msgid "The request %s failed: %s."
msgstr "请求 %s 失败:%s。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:303
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:307
msgid "The response code."
msgstr "响应代码。"
@@ -2966,11 +2993,11 @@ msgstr ""
"这是 UDP over TCP 协议的 TUIC 版本,旨在提供 TUIC 本身不具备的基于 QUIC 流的 "
"UDP 中继模式。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:176
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:119
msgid "ThreatBook Public DNS (117.50.10.10)"
msgstr "微步在线公共 DNS(117.50.10.10)"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:370
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:378
msgid ""
"Timeout for UDP connections.
Setting a larger value than the UDP timeout "
"in inbounds will have no effect."
@@ -2983,14 +3010,14 @@ msgid ""
msgstr ""
"要与 Xray-core 兼容,请将其设置为 Sec-WebSocket-Protocol。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:130
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:134
msgid ""
"To enable Tun support, you need to install ip-full and "
"kmod-tun"
msgstr ""
"要启用 Tun 支持,你需要安装 ip-full 和 kmod-tun。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:178
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:210
msgid "Trace"
msgstr "跟踪"
@@ -3006,7 +3033,7 @@ msgstr "Trojan"
msgid "Tuic"
msgstr "Tuic"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:128
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:132
msgid "Tun TCP/UDP"
msgstr "Tun TCP/UDP"
@@ -3031,7 +3058,7 @@ msgstr "UDP 分片"
msgid "UDP NAT Settings"
msgstr "UDP NAT 设置"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:182
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:186
#: htdocs/luci-static/resources/view/homeproxy-pro/server.js:614
msgid "UDP NAT expiration time"
msgstr "UDP NAT 过期时间"
@@ -3064,7 +3091,7 @@ msgstr "UDP 包中继模式。"
msgid "UDP relay mode"
msgstr "UDP 中继模式"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:369
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:377
msgid "UDP timeout"
msgstr "UDP 超时"
@@ -3084,7 +3111,7 @@ msgstr "URLTest 节点"
msgid "UUID"
msgstr "UUID"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:132
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:164
msgid "Unknown error."
msgstr "未知错误。"
@@ -3104,15 +3131,15 @@ msgstr "不支持的指纹!"
msgid "Update %s subscriptions"
msgstr "更新 %s 个订阅"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:123
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:155
msgid "Update failed."
msgstr "更新失败。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:132
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:137
msgid "Update interval"
msgstr "更新间隔"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:133
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:138
msgid "Update interval of rule set."
msgstr "规则集更新间隔。"
@@ -3169,7 +3196,7 @@ msgstr "上传..."
msgid "Use ACME TLS certificate issuer."
msgstr "使用 ACME TLS 证书颁发机构。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:144
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:148
msgid ""
"Use the tutorial-recommended sniffer profile (100ms timeout, http/tls/stun/"
"quic/dns). Default keeps the 300ms / unconstrained-list behaviour."
@@ -3187,7 +3214,7 @@ msgstr "用于验证返回证书上的主机名。如允许不安全连接,此
msgid "Used to verify the hostname on the returned certificates."
msgstr "用于验证返回证书上的主机名。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:136
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:137
msgid "User"
msgstr "用户"
@@ -3212,15 +3239,15 @@ msgstr "VLESS"
msgid "VMess"
msgstr "VMess"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:172
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:115
msgid "WAN DNS (read from interface)"
msgstr "WAN DNS(从接口获取)"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:90
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:91
msgid "WAN IP Policy"
msgstr "WAN IP 策略"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:181
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:213
msgid "Warn"
msgstr "警告"
@@ -3228,7 +3255,7 @@ msgstr "警告"
msgid "WebSocket"
msgstr "WebSocket"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:205
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:148
msgid ""
"When the main DNS returns a mainland China IP, re-resolve via China DNS "
"using evaluate/match_response."
@@ -3272,12 +3299,16 @@ msgstr "您的 %s 已成功上传。大小:%sB。"
msgid "ZeroSSL"
msgstr "ZeroSSL"
+#: htdocs/luci-static/resources/homeproxy-pro.js:1154
+msgid "a numeric code or an uppercase type name (for example AAAA)"
+msgstr "数字代码或大写类型名(例如 AAAA)"
+
#: htdocs/luci-static/resources/view/homeproxy-pro/node.js:438
#: htdocs/luci-static/resources/view/homeproxy-pro/server.js:527
msgid "certificate"
msgstr "证书"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:364
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:371
msgid "connect UDP connections"
msgstr "主动连接 UDP"
@@ -3286,6 +3317,10 @@ msgstr "主动连接 UDP"
msgid "default"
msgstr "默认"
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:127
+msgid "empty: clients may be bypassing this router's DNS"
+msgstr "为空:客户端可能绕过了本路由器的 DNS"
+
#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:91
msgid "failed"
msgstr "失败"
@@ -3302,7 +3337,7 @@ msgstr "gRPC 允许无活动连接"
msgid "gRPC service name"
msgstr "gRPC 服务名称"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:158
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:162
msgid "gVisor"
msgstr "gVisor"
@@ -3312,12 +3347,12 @@ msgid "http"
msgstr ""
#: htdocs/luci-static/resources/homeproxy-pro.js:220
-#: htdocs/luci-static/resources/homeproxy-pro.js:1134
-#: htdocs/luci-static/resources/homeproxy-pro.js:1154
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:145
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:183
+#: htdocs/luci-static/resources/homeproxy-pro.js:1163
+#: htdocs/luci-static/resources/homeproxy-pro.js:1183
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:86
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:126
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:141
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:72
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:77
#: htdocs/luci-static/resources/view/homeproxy-pro/node.js:484
msgid "non-empty value"
msgstr "非空值"
@@ -3330,6 +3365,10 @@ msgstr "非空值"
msgid "none"
msgstr "无"
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:115
+msgid "not installed"
+msgstr "未安装"
+
#: htdocs/luci-static/resources/homeproxy-pro.js:632
#: htdocs/luci-static/resources/view/homeproxy-pro/node.js:776
msgid "packet addr (v2ray-core v5+)"
@@ -3343,11 +3382,11 @@ msgstr "通过"
msgid "private key"
msgstr "私钥"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:127
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:128
msgid "quic-go / uquic chrome"
msgstr "quic-go / uquic chrome"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:302
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:337
msgid "sing-box client"
msgstr "sing-box 客户端"
@@ -3375,7 +3414,7 @@ msgstr ""
"sing-box 将 Snell v4/v5 线协议统一为 v4,另提供 v6;v6 的预共享密钥(上方密"
"码)长度须为 12-255 字节。"
-#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:305
+#: htdocs/luci-static/resources/view/homeproxy-pro/status.js:340
msgid "sing-box server"
msgstr "sing-box 服务端"
@@ -3399,7 +3438,7 @@ msgstr "未检查"
msgid "unique UCI identifier"
msgstr "独立 UCI 标识"
-#: htdocs/luci-static/resources/homeproxy-pro.js:1145
+#: htdocs/luci-static/resources/homeproxy-pro.js:1174
msgid "unique value"
msgstr "独立值"
@@ -3436,17 +3475,17 @@ msgstr ""
msgid "v6"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:157
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:161
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:194
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:198
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:98
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:102
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:137
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/dns.js:141
msgid "valid DNS server address"
msgstr "有效 DNS 服务器地址"
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:155
#: htdocs/luci-static/resources/view/homeproxy-pro/client/nodes.js:158
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:80
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:83
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:85
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:88
#: htdocs/luci-static/resources/view/homeproxy-pro/node.js:741
#: htdocs/luci-static/resources/view/homeproxy-pro/node.js:744
msgid "valid URL"
@@ -3456,8 +3495,8 @@ msgstr "有效网址"
msgid "valid base64 key with %d characters"
msgstr "包含 %d 个字符的有效 base64 密钥"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:158
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:203
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:159
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/access.js:204
msgid "valid hostname"
msgstr "有效主机名"
@@ -3465,43 +3504,58 @@ msgstr "有效主机名"
msgid "valid port range (port1:port2)"
msgstr "有效端口范围(port1:port2)"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:112
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/routing.js:116
msgid "valid port value"
msgstr "有效端口值"
-#: htdocs/luci-static/resources/homeproxy-pro.js:1156
+#: htdocs/luci-static/resources/homeproxy-pro.js:1140
+msgid "valid regular expression"
+msgstr "有效的正则表达式"
+
+#: htdocs/luci-static/resources/homeproxy-pro.js:1185
msgid "valid uuid"
msgstr "有效 uuid"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:403
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:417
msgid "wrong-ack"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:402
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:416
msgid "wrong-checksum"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:404
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:418
msgid "wrong-md5"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:401
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:415
msgid "wrong-sequence (default)"
msgstr "wrong-sequence(默认)"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:405
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/common.js:419
msgid "wrong-timestamp"
msgstr ""
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:75
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:123
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:80
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:128
msgid "{tag} in URL when extra tags are used"
msgstr "使用额外标签时 URL 需包含 {tag}"
-#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:126
+#: htdocs/luci-static/resources/view/homeproxy-pro/client/subscription.js:131
msgid "{tag} in initial path when extra tags are used"
msgstr "使用额外标签时初始路径需包含 {tag}"
+#~ msgid ""
+#~ "Resolves the domains on the China path, so it has to be a domestic "
+#~ "service to get the correct local CDN answers. Keep the default; the "
+#~ "upstream may block UDP/53, in which case a DoH address (https://"
+#~ "dns.alidns.com/dns-query, https://doh.pub/dns-query) also works. Support "
+#~ "UDP, TCP, DoH, DoQ, DoT."
+#~ msgstr ""
+#~ "解析中国路径上的域名,必须是国内服务才能获得正确的本地 CDN 结果。建议保持"
+#~ "默认值;若上游封禁 UDP/53,也可使用 DoH 地址(https://dns.alidns.com/dns-"
+#~ "query、https://doh.pub/dns-query)。支持 UDP、TCP、DoH、DoQ、DoT。"
+
#~ msgid "Bootstrap DNS server"
#~ msgstr "引导 DNS 服务器"
diff --git a/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/firewall_post.ut b/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/firewall_post.ut
index 7bd2c53ba..6554a0706 100755
--- a/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/firewall_post.ut
+++ b/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/firewall_post.ut
@@ -281,7 +281,12 @@ set homeproxy_mainland_addr_v4 {
{% endfor %}
}
}
-{% if ((ipv6_support === '1') || china_dns_server): %}
+{# The v6 mainland set is only ever matched inside `ipv6_support === '1'`
+ blocks - every reference to it below is gated on that - so the old
+ `|| china_dns_server` alternative only ever built a set nothing pointed
+ at: 1033 prefixes loaded into the kernel on every fw4 reload for nothing.
+ china_dns_server is a DNS-side setting and has no bearing on v6 routing. #}
+{% if (ipv6_support === '1'): %}
set homeproxy_mainland_addr_v6 {
type ipv6_addr
flags interval
@@ -347,6 +352,25 @@ set homeproxy_routing_port {
}
{% endif %}
+{# IPv6 forwarding is refused outright when the switch is off.
+ The DNS side already answers AAAA with REFUSED, but that only covers clients
+ that ask *this* router: a cached address, a client with its own resolver, or
+ an IPv6-only destination still reaches the WAN and bypasses both the proxy
+ and the mainland split.
+ fw4's own forward chain cannot be used for this - it jumps into the zone
+ chains, whose accept rules end the traversal before anything appended at the
+ end of the chain is reached - so this is a base chain of its own, ordered
+ before fw4's forward (priority filter == 0).
+ Intra-LAN IPv6 is covered as well, which is what "this router does not do
+ IPv6" means for `ipv6_support` off. #}
+{% if (ipv6_support !== '1'): %}
+chain homeproxy_ipv6_guard {
+ type filter hook forward priority mangle; policy accept
+
+ meta nfproto ipv6 counter reject comment "!{{ cfgname }}: IPv6 forwarding is disabled (ipv6_support is off)"
+}
+{% endif %}
+
{# DNS hijack & TCP redirect #}
chain dstnat {
{% if ((dns_redirect === '1') && (dns_hijacked !== '1')): %}
@@ -562,14 +586,14 @@ chain homeproxy_mangle_prerouting {
{% if (ipv6_support === '1'): %}
ip6 daddr != @homeproxy_gfw_list_v6 counter return
{% endif /* ipv6_support */ %}
- udp dport { 80, 443 } counter reject comment "!{{ cfgname }}: Block QUIC (not proxyable)"
+ udp dport { 80, 443 } counter reject comment "!{{ cfgname }}: reject QUIC (UDP 80/443), clients fall back to TCP"
{% elif (routing_mode === 'bypass_mainland_china' || bypass_cn_traffic === '1'): %}
ip daddr @homeproxy_mainland_addr_v4 counter return
{% if (ipv6_support === '1'): %}
ip6 daddr @homeproxy_mainland_addr_v6 counter return
{% endif /* ipv6_support */ %}
{% if (routing_mode !== 'custom'): %}
- udp dport { 80, 443 } counter reject comment "!{{ cfgname }}: Block QUIC (not proxyable)"
+ udp dport { 80, 443 } counter reject comment "!{{ cfgname }}: reject QUIC (UDP 80/443), clients fall back to TCP"
{% endif /* routing_mode */ %}
{% elif (routing_mode === 'proxy_mainland_china'): %}
ip daddr != @homeproxy_mainland_addr_v4 counter return
@@ -723,14 +747,14 @@ chain homeproxy_mangle_tun {
{% if (ipv6_support === '1'): %}
ip6 daddr != @homeproxy_gfw_list_v6 counter return
{% endif /* ipv6_support */ %}
- udp dport { 80, 443 } counter reject comment "!{{ cfgname }}: Block QUIC (not proxyable)"
+ udp dport { 80, 443 } counter reject comment "!{{ cfgname }}: reject QUIC (UDP 80/443), clients fall back to TCP"
{% elif (routing_mode === 'bypass_mainland_china' || bypass_cn_traffic === '1'): %}
ip daddr @homeproxy_mainland_addr_v4 counter return
{% if (ipv6_support === '1'): %}
ip6 daddr @homeproxy_mainland_addr_v6 counter return
{% endif /* ipv6_support */ %}
{% if (routing_mode !== 'custom'): %}
- udp dport { 80, 443 } counter reject comment "!{{ cfgname }}: Block QUIC (not proxyable)"
+ udp dport { 80, 443 } counter reject comment "!{{ cfgname }}: reject QUIC (UDP 80/443), clients fall back to TCP"
{% endif /* routing_mode */ %}
{% elif (routing_mode === 'proxy_mainland_china'): %}
ip daddr != @homeproxy_mainland_addr_v4 counter return
diff --git a/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/fw4_names.sh b/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/fw4_names.sh
index 66fe94f73..e09d1b0fa 100644
--- a/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/fw4_names.sh
+++ b/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/fw4_names.sh
@@ -31,6 +31,7 @@ HP_FW4_CHAINS="
homeproxy_mangle_mark
homeproxy_mangle_tun
homeproxy_mangle_tun_mark
+ homeproxy_ipv6_guard
"
HP_FW4_SETS="
diff --git a/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generate_client.uc b/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generate_client.uc
index d1bae8a85..5a1a238ae 100755
--- a/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generate_client.uc
+++ b/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generate_client.uc
@@ -25,7 +25,7 @@
'use strict';
import { connect } from 'ubus';
-import { mkdtemp, readfile, writefile } from 'fs';
+import { mkdtemp, open, readfile, writefile } from 'fs';
import { Loader } from './config/loader.uc';
import { generate } from './generator/client.uc';
@@ -52,9 +52,23 @@ function resolve_env(dm) {
const env = {
wan_dns: (ubus?.call('network.interface', 'status', {'interface': 'wan'}))?.['dns-server']?.[0],
direct_domain_list: [],
- proxy_domain_list: []
+ proxy_domain_list: [],
+ china_ip_ruleset_available: false
};
+ /* The route block references this file as a `type: local` rule-set, and
+ * sing-box refuses the whole configuration when it cannot open the path.
+ * service.sh generates it before the client configuration, so it is
+ * normally there; a fresh install whose generation failed (no ucode, a
+ * read-only filesystem, a full disk) is what this answers. The generator
+ * is a pure function, so the existence check belongs on this side of the
+ * boundary. open() rather than readfile(): the file is a few megabytes
+ * and only its existence matters. */
+ const china_ip_json = open(HP_DIR + '/resources/china_ip4.json');
+ env.china_ip_ruleset_available = china_ip_json != null;
+ if (china_ip_json)
+ china_ip_json.close();
+
if (routing_mode !== 'custom') {
const direct_list_raw = readfile(HP_DIR + '/resources/direct_list.txt');
env.direct_domain_list = direct_list_raw ? split(trim(direct_list_raw), /[\r\n]/) : [];
diff --git a/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generator/common.uc b/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generator/common.uc
index 82395bc07..d06176c5f 100644
--- a/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generator/common.uc
+++ b/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generator/common.uc
@@ -52,6 +52,15 @@ export function get_outbound(cfg, dm) {
return cfg;
default:
const rn = ConfigQuery.find_by_name(dm.routing.nodes, cfg);
+ /* A routing_node that was disabled after something started referring
+ * to it is skipped by generate_outbound(), so the tag returned below
+ * would exist nowhere: sing-box refuses the whole configuration with
+ * "outbound not found: cfg--out", which does not name the node
+ * the user actually turned off. Fail here instead, the way
+ * get_resolver()/get_ruleset() already do. The caller passes the
+ * section name, so it is what the message names. */
+ if (rn && (rn.enabled === false || rn.enabled === '0'))
+ die(sprintf("the routing node '%s' is disabled; enable it or point the rule at another outbound.", cfg));
if (!rn || isEmpty(rn.node))
die(sprintf("%s's node is missing, please check your configuration.", cfg));
else if (rn.node === 'urltest')
diff --git a/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generator/context.uc b/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generator/context.uc
index 9fadeeff8..bd0d86d84 100644
--- a/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generator/context.uc
+++ b/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generator/context.uc
@@ -153,11 +153,11 @@ export function build_context(dm, env) {
* its own outbound/rule tables (routing.default_outbound plus the
* routing_node / routing_rule / ruleset sections). A residual
* config.main_node must therefore not switch the generator back to
- * the preset path: LuCI hides that field in custom mode but does not
- * clear it (`rmempty = false`), so a router that was configured in a
- * preset mode and then switched to custom keeps the old value in
- * UCI - and the pre-refactor code read it only when the mode was not
- * custom. Reading it here anyway silently dropped every custom
+ * the preset path. LuCI itself does clear a field the user can no
+ * longer see (depends() makes parse() remove the value), but UCI is
+ * writable from anywhere - a script, an older version, a hand edit -
+ * and the pre-refactor code read this value only when the mode was
+ * not custom. Reading it here anyway silently dropped every custom
* routing rule and made route.final 'main-out'. */
main_node: (routing_mode === 'custom') ? null : dm.general.main_node,
main_udp_node: (routing_mode === 'custom') ? null : dm.general.main_udp_node,
@@ -248,6 +248,14 @@ export function build_context(dm, env) {
ctx.proxy_domain_list = env.proxy_domain_list || [];
}
+ /* Whether the local china-ip rule-set file exists (the CLI resolved it;
+ * see generate_client.uc). route.uc emits that rule-set - and the rule
+ * that matches it - only when it does, so a file that is missing degrades
+ * the mainland split to geoip-cn instead of making sing-box refuse the
+ * whole configuration over a path it cannot open. A caller that does not
+ * supply the flag keeps the previous behaviour. */
+ ctx.china_ip_ruleset_available = env.china_ip_ruleset_available !== false;
+
/* `dedicated_udp_node` means main_udp_node was set to a different
* proxy-able node than main_node (the 'same'/'nil' sentinels don't
* qualify). Kept on ctx so both build_outbounds and build_route
diff --git a/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generator/dns.uc b/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generator/dns.uc
index f3dfa1787..1a2eb2b7a 100644
--- a/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generator/dns.uc
+++ b/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generator/dns.uc
@@ -72,6 +72,50 @@ function parse_dnsquery(strquery) {
return querys;
}
+/* Build the sing-box `headers` object from the UCI string list.
+ *
+ * sing-box types this field as `badoption.HTTPHeader`, i.e.
+ * map[string][]string; an array (or a bare string) fails to decode and the
+ * whole configuration is rejected. The LuCI form is a DynamicList, so every
+ * entry lands in UCI as a string and the two sides disagreed on the shape:
+ * `"headers": ["Name: value"]` is not a map.
+ *
+ * The form keeps its list widget and one entry is written `Name: Value`;
+ * repeating a name appends to that name's value list, which is how HTTP
+ * expresses a multi-valued header. An entry that does not fit the form is
+ * named and rejected here rather than left for sing-box to report as
+ * "cannot unmarshal array into Go value of type badoption.HTTPHeader". */
+function parse_http_headers(list, where) {
+ if (isEmpty(list))
+ return null;
+
+ /* A single hand-written UCI option arrives as a string, not a list. */
+ if (type(list) !== 'array')
+ list = [ list ];
+
+ const headers = {};
+ for (let item in list) {
+ const line = trim(item);
+ if (line === '')
+ continue;
+
+ const sep = index(line, ':');
+ if (sep <= 0)
+ die(sprintf("homeproxy-pro: %s: header '%s' is not in 'Name: Value' form.", where, line));
+
+ const name = trim(substr(line, 0, sep));
+ const value = trim(substr(line, sep + 1));
+ if (isEmpty(name) || isEmpty(value))
+ die(sprintf("homeproxy-pro: %s: header '%s' is not in 'Name: Value' form.", where, line));
+
+ if (!headers[name])
+ headers[name] = [];
+ push(headers[name], value);
+ }
+
+ return isEmpty(keys(headers)) ? null : headers;
+}
+
/* --- proxy routing-mode path (bypass_mainland_china / proxy / / gfwlist) -- */
/* Domains whose NAPTR (qtype 35) queries bypass china-dns. Aliyun
@@ -87,7 +131,6 @@ function parse_dnsquery(strquery) {
* someone reads the comment and thinks "I know a domain that should
* go direct". */
const NAPTR_BYPASS_SUFFIXES = [
- 'r.10086.cn',
'10086.cn',
'pub.3gppnetwork.org'
];
@@ -194,7 +237,16 @@ function append_proxy_dns(config, dm, ctx) {
server: 'main-dns'
});
- /* Reject SVCB/HTTPS queries to avoid proxy DNS timeout on null domains */
+ /* Reject SVCB/HTTPS queries: a client that learns the address from these
+ * record types dials the embedded IP without asking again, which is what
+ * lets a query slip past the DNS split (and, in the custom path, past
+ * Fake-IP).
+ *
+ * It sits after the two domain lists on purpose, so a domain the user put
+ * on a list keeps whatever resolver that list chose - the custom path
+ * below puts the same reject first instead, because there the user's own
+ * rules are the ones that must not be shadowed. The two orders are
+ * deliberate; do not "fix" one into the other. */
push(config.dns.rules, {
query_type: [64, 65],
action: 'reject'
@@ -317,7 +369,7 @@ function append_custom_dns(config, dm, ctx) {
server: cfg.server,
server_port: strToInt(cfg.server_port),
path: cfg.path,
- headers: cfg.headers,
+ headers: parse_http_headers(cfg.headers, sprintf("dns server '%s'", cfg.name)),
tls: cfg.tls_sni ? {
enabled: true,
server_name: cfg.tls_sni
@@ -385,24 +437,71 @@ function append_custom_dns(config, dm, ctx) {
rule_set: get_ruleset(cfg.rule_set, dm),
rule_set_ip_cidr_match_source: strToBool(cfg.rule_set_ip_cidr_match_source),
invert: strToBool(cfg.invert),
- race: strToBool(cfg.race),
- speculative: strToBool(cfg.speculative),
action: cfg.action
};
- /* `route` (and `evaluate`, which needs a server for the eval step).
- * `resolve` is also a possibility but is not currently exposed in
- * the UI; if it ever is, this branch is where it lands. */
- if (cfg.action === 'route' || cfg.action === 'evaluate' || cfg.action === 'resolve') {
+ /* race/speculative are action fields, not match fields, and their
+ * legality is decided at load time by sing-box's
+ * validateDNSRuleAction() (route/rule/rule_dns.go):
+ *
+ * - race and speculative are mutually exclusive ("`race` and
+ * `speculative` cannot be combined on the same rule");
+ * - race is only allowed on a final action - route, respond,
+ * reject, predefined - and "`race` requires a final action"
+ * otherwise;
+ * - speculative exists only in the route and evaluate option
+ * structs, so on any other action it is an unknown field and
+ * DisallowUnknownFields rejects the whole configuration.
+ *
+ * The old code emitted both unconditionally. The form hides them
+ * with depends(), but LuCI does not delete the value of a hidden
+ * option, so "tick race on a route rule, then switch the rule to
+ * evaluate" left an illegal value behind. Gate by action and die
+ * on the combination, naming the field rather than letting sing-box
+ * answer with "unknown field". */
+ const want_race = strToBool(cfg.race) === true,
+ want_speculative = strToBool(cfg.speculative) === true;
+
+ if (want_race && want_speculative)
+ die(sprintf("homeproxy-pro: DNS rule '%s' enables both race and speculative; sing-box refuses that combination.", cfg.name));
+
+ if (want_race) {
+ if (!(cfg.action in ['route', 'reject', 'predefined']))
+ die(sprintf("homeproxy-pro: DNS rule '%s' uses race with action '%s'; race is only allowed on a final action (route, reject, predefined).", cfg.name, cfg.action));
+ rule.race = true;
+ }
+
+ if (want_speculative) {
+ if (!(cfg.action in ['route', 'evaluate']))
+ die(sprintf("homeproxy-pro: DNS rule '%s' uses speculative with action '%s'; speculative is only valid for route and evaluate.", cfg.name, cfg.action));
+ rule.speculative = true;
+ }
+
+ /* `route` and `evaluate` (which needs a server for the eval step).
+ * DNS rules have no `resolve` action - that one belongs to the route
+ * rules - and the form does not offer it here. It used to be handled
+ * anyway, which would have emitted an action sing-box does not know
+ * the moment a stale UCI value carried it. */
+ if (cfg.action === 'route' || cfg.action === 'evaluate') {
rule.server = get_resolver(cfg.server, dm);
rule.disable_cache = strToBool(cfg.dns_disable_cache);
rule.disable_optimistic_cache = strToBool(cfg.disable_optimistic_cache);
rule.rewrite_ttl = strToInt(cfg.rewrite_ttl);
+ /* Load-time mutual exclusion in validateDNSRuleAction():
+ * "`client_subnet` and `remove_client_subnet` are mutually
+ * exclusive". The form draws them as two independent
+ * controls, so both can be left set. */
+ if (!isEmpty(cfg.client_subnet) && strToBool(cfg.remove_client_subnet) === true)
+ die(sprintf("homeproxy-pro: DNS rule '%s' sets both client_subnet and remove_client_subnet; sing-box refuses that combination.", cfg.name));
rule.client_subnet = cfg.client_subnet;
rule.remove_client_subnet = strToBool(cfg.remove_client_subnet);
}
- if (cfg.action === 'route' || cfg.action === 'resolve')
+ /* 1.14's evaluate action carries the same timeout override as route
+ * (DNSEvaluateActionOptions embeds AbstractDNSRouteActionOptions), and
+ * the form shows the field for both actions. Emitting it for route
+ * only made the value disappear silently on an evaluate rule. */
+ if (cfg.action === 'route' || cfg.action === 'evaluate')
rule.timeout = strToTime(cfg.dns_timeout);
/* `reject` only accepts method + no_drop. Anything else here is
@@ -476,6 +575,13 @@ function append_custom_dns(config, dm, ctx) {
rule.response_extra = cfg.response_extra;
}
+ /* race also requires match_response in 1.14. This check has to sit
+ * after every match_response assignment: the legacy address-filter
+ * wrapping above gives one to those rules too, and that satisfies
+ * sing-box just as well. */
+ if (want_race && !rule.match_response)
+ die(sprintf("homeproxy-pro: DNS rule '%s' enables race without match_response; sing-box requires one.", cfg.name));
+
push(builtin_dns_rules, rule);
}
config.dns.rules = builtin_dns_rules;
@@ -483,6 +589,31 @@ function append_custom_dns(config, dm, ctx) {
config.dns.final = get_resolver(ctx.dns_default_server, dm);
}
+/* Reject AAAA when this router does not proxy IPv6.
+ *
+ * Every IPv6 rule-set and rule in firewall_post.ut sits inside
+ * `ipv6_support === '1'`, so with the switch off the router does not
+ * intercept v6 at all - and nothing in this DNS block ever refused a AAAA
+ * query. dns.strategy=ipv4_only on the preset paths is a resolution
+ * preference, not a query block, and custom routing takes the UCI default
+ * (prefer_ipv4). A dual-stack LAN therefore still received AAAA records and
+ * connected straight out over the WAN, bypassing both the proxied resolver
+ * and the destination-based split.
+ *
+ * Appended last so a user rule keeps precedence over it. A client with its
+ * own DoH/DoT can still bypass this layer; a hard block would have to be an
+ * nft rule, which changes the user-visible "IPv6 still works" behaviour of
+ * the switch and is therefore not done here. */
+function append_ipv6_guard(config, ctx) {
+ if (ctx.ipv6_support === '1')
+ return;
+
+ push(config.dns.rules, {
+ query_type: [28],
+ action: 'reject'
+ });
+}
+
/* --- public entry ------------------------------------------------------ */
/* The always-on DNS block. Every routing mode (proxy and custom) gets
@@ -554,5 +685,7 @@ export function build_dns(config, dm, ctx) {
append_proxy_dns(config, dm, ctx);
}
+ append_ipv6_guard(config, ctx);
+
return config;
};
\ No newline at end of file
diff --git a/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generator/outbound.uc b/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generator/outbound.uc
index ddd725794..20cc226a8 100644
--- a/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generator/outbound.uc
+++ b/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generator/outbound.uc
@@ -253,7 +253,7 @@ function buildCustomOutbounds(dm, config, ctx, direct_overrides) {
* comes from a routing_node. `|| {}` made it look non-null and the
* failure surfaced as a null dereference two lines later. */
if (isEmpty(outbound))
- die(sprintf("routing node '%s' refers to a node that does not exist.\n", cfg['.name']));
+ die(sprintf("routing node '%s' refers to a node that does not exist.\n", cfg.name));
if (outbound.type === 'wireguard') {
/* WireGuard goes through generate_endpoint() which tags
@@ -274,7 +274,7 @@ function buildCustomOutbounds(dm, config, ctx, direct_overrides) {
const ob = generate_outbound(outbound, mark, direct_overrides);
if (ob == null)
- die(sprintf("routing node '%s' cannot be built from node '%s'.\n", cfg['.name'], cfg.node));
+ die(sprintf("routing node '%s' cannot be built from node '%s'.\n", cfg.name, cfg.node));
ob.bind_interface = cfg.bind_interface;
ob.detour = get_outbound(cfg.outbound, dm);
diff --git a/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generator/route.uc b/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generator/route.uc
index 65319325d..566b7b389 100644
--- a/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generator/route.uc
+++ b/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generator/route.uc
@@ -120,17 +120,23 @@ function build_route_proxy(config, dm, ctx, direct_overrides) {
outbound: ctx.proxy_fallback ? 'direct-out' : 'main-out'
});
}
- if (ctx.routing_mode === 'bypass_mainland_china') {
- /* geoip-cn.srs is upstream's list; the firewall's mainland set is
- * built from our own china_ip4.txt (firewall_post.ut reads it
- * directly). The two disagree - the bundled list has 8.152.0.0/13
- * (Alibaba Cloud) and geoip-cn.srs does not - so a connection into
- * that range passes the firewall as "not mainland" and would then be
- * sent direct by the rule above instead of to the proxy. This local
- * rule-set is generated from the same china_ip4.txt, which is what
- * makes both sides decide from one list. In
- * proxy_mainland_china it is not needed twice: the geoip-cn rule
- * already selects the mainland side for the proxy. */
+ /* geoip-cn.srs is upstream's list; the firewall's mainland set is
+ * built from our own china_ip4.txt (firewall_post.ut reads it
+ * directly). The two disagree - the bundled list has 8.152.0.0/13
+ * (Alibaba Cloud) and geoip-cn.srs does not - so a connection into
+ * that range passes the firewall as "not mainland" and would then be
+ * sent direct by the rule above instead of to the proxy. This local
+ * rule-set is generated from the same china_ip4.txt, which is what
+ * makes both sides decide from one list. In
+ * proxy_mainland_china it is not needed twice: the geoip-cn rule
+ * already selects the mainland side for the proxy.
+ *
+ * Only when the file is actually there: service.sh generates it before
+ * the client configuration, but a fresh install whose generation failed
+ * would otherwise leave a `type: local` rule-set pointing at a path
+ * sing-box cannot open, which fails the whole configuration rather than
+ * this one rule. The geoip-cn rule above still covers the split. */
+ if (ctx.routing_mode === 'bypass_mainland_china' && ctx.china_ip_ruleset_available) {
push(config.route.rules, {
rule_set: 'china-ip',
action: 'route',
@@ -252,13 +258,14 @@ function build_route_proxy(config, dm, ctx, direct_overrides) {
* file the running instance reloads it in place - no restart and,
* more importantly, no second copy of the list to keep in sync.
* China IPv6 is deliberately absent: the firewall only fills the v6
- * set when ipv6_support is on, and the client refuses to resolve
- * AAAA when it is off. */
- push(config.route.rule_set, {
- type: 'local',
- tag: 'china-ip',
- path: HP_DIR + '/resources/china_ip4.json'
- });
+ * set when ipv6_support is on. Declared only when the file exists,
+ * for the same reason as the rule that matches it (see above). */
+ if (ctx.china_ip_ruleset_available)
+ push(config.route.rule_set, {
+ type: 'local',
+ tag: 'china-ip',
+ path: HP_DIR + '/resources/china_ip4.json'
+ });
}
if (isEmpty(config.route.rule_set))
@@ -303,7 +310,6 @@ function build_route_custom(config, dm, ctx, direct_overrides) {
ip_version: strToInt(cfg.ip_version),
protocol: cfg.protocol,
network: cfg.network,
- client: cfg.client,
domain: cfg.domain,
domain_suffix: cfg.domain_suffix,
domain_keyword: cfg.domain_keyword,
@@ -328,6 +334,15 @@ function build_route_custom(config, dm, ctx, direct_overrides) {
source_hostname: cfg.source_hostname
};
+ /* The sniffed client type is only set for QUIC and SSH traffic; the
+ * form offers the field under those two protocols only, but a value
+ * left behind by an earlier protocol would make the rule match
+ * nothing, so it is emitted only when it can apply. */
+ const protocols = type(cfg.protocol) === 'array' ? cfg.protocol
+ : (isEmpty(cfg.protocol) ? [] : [ cfg.protocol ]);
+ if ('quic' in protocols || 'ssh' in protocols)
+ rule.client = cfg.client;
+
/* `route-options` only takes the override_* fields; `outbound` is the
* `route` action's field. The two used to share one branch and
* `route-options` ended up carrying an `outbound` sing-box then
@@ -347,6 +362,39 @@ function build_route_custom(config, dm, ctx, direct_overrides) {
rule.tls_spoof = cfg.tls_spoof || null;
rule.tls_spoof_method = cfg.tls_spoof_method || null;
}
+
+ /* Load-time mutual exclusion in RouteOptionsActionOptions.UnmarshalJSON()
+ * (option/rule_action.go): "`tls_fragment` and `tls_record_fragment`
+ * are mutually exclusive". The form draws both switches for the
+ * route action and neither hides the other, so a user following the
+ * description ("try TLS record fragment first") can easily tick
+ * both and lose the whole configuration. */
+ if (strToBool(cfg.tls_fragment) === true && strToBool(cfg.tls_record_fragment) === true)
+ die(sprintf("homeproxy-pro: routing rule '%s' enables both tls_fragment and tls_record_fragment; sing-box refuses that combination.", cfg.name));
+
+ /* The same unmarshaller rejects an empty action object with "empty
+ * route option action". Match fields belong to the rule, not to the
+ * action payload, so a rule that selects Route options and fills in
+ * none of the fields above fails the whole configuration with a
+ * message that names nothing. The fields are judged on their parsed
+ * values, not on the raw UCI strings: a UCI '0' becomes `false`,
+ * which is the Go zero value and is not payload. */
+ if (cfg.action === 'route-options') {
+ const has_payload =
+ !isEmpty(rule_override_address) ||
+ !isEmpty(rule_override_port) ||
+ strToBool(cfg.udp_disable_domain_unmapping) === true ||
+ strToBool(cfg.udp_connect) === true ||
+ !isEmpty(cfg.udp_timeout) ||
+ strToBool(cfg.tls_fragment) === true ||
+ !isEmpty(cfg.tls_fragment_fallback_delay) ||
+ strToBool(cfg.tls_record_fragment) === true ||
+ !isEmpty(cfg.tls_spoof) ||
+ !isEmpty(cfg.tls_spoof_method);
+
+ if (!has_payload)
+ die(sprintf("homeproxy-pro: routing rule '%s' selects the route-options action but sets none of its fields; sing-box rejects an empty route option action.", cfg.name));
+ }
/* `route` alone is the action that picks a destination - `route-options`
* adjusts the existing connection, so it does not take `outbound`. */
if (cfg.action === 'route')
diff --git a/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generator/ruleset.uc b/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generator/ruleset.uc
index 374ef3446..ef6ec55ed 100644
--- a/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generator/ruleset.uc
+++ b/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/generator/ruleset.uc
@@ -26,7 +26,7 @@
'use strict';
-import { isEmpty, validateHomeProxyPath } from '../homeproxy-pro.uc';
+import { isEmpty, strToTime, validateHomeProxyPath } from '../homeproxy-pro.uc';
import { get_outbound, isDirectOutboundTag } from './common.uc';
@@ -40,6 +40,13 @@ export function build_user_rulesets(rule_set_array, dm, ctx) {
if (!cfg.enabled)
continue;
+ /* An inline rule-set is a `rules` array, and nothing in this package
+ * ever writes one: the form only offers local and remote. Emitting
+ * `{type: 'inline'}` without `rules` is rejected by sing-box, so a
+ * hand-written UCI section is refused by name here. */
+ if (cfg.type === 'inline')
+ die(sprintf("homeproxy-pro: rule-set '%s' is declared inline, which this package cannot edit; use a local or remote rule-set.", cfg.name));
+
const extra_tags = cfg.extra_tags || [];
let rs_tag = 'cfg-' + cfg.name + '-rule';
if (length(extra_tags) && cfg.type !== 'inline') {
@@ -78,18 +85,29 @@ export function build_user_rulesets(rule_set_array, dm, ctx) {
format: cfg.format,
path: cfg.path,
url: cfg.url,
- update_interval: cfg.update_interval
+ /* A duration to sing-box: a bare number is rejected with
+ * "time: missing unit in duration", taking the whole
+ * configuration down, and the form only offers a placeholder
+ * ("1d"), not a format check. Same treatment as the DNS
+ * timeouts. */
+ update_interval: strToTime(cfg.update_interval)
};
- /* download_detour is a pre-1.14 option that only makes sense for
- remote rule-sets; emitting it for local/inline ones makes sing-box
- * 1.14 reject the whole config. It is translated into http_clients
- right below. */
+ /* download_detour is deprecated since 1.14 (removed in 1.16) and only
+ * ever applied to remote rule-sets; emitting it at all makes sing-box
+ * refuse the configuration. It is translated into http_clients right
+ * below. */
if (cfg.type === 'remote')
ruleset.download_detour = get_outbound(cfg.outbound, dm) || get_outbound(ctx.default_outbound, dm);
- if (cfg.type === 'remote' && !isEmpty(cfg.initial_path))
- /* initial_path is read off the local disk as root; same
- * whitelist as the local ruleset path. */
- ruleset.initial_path = validateHomeProxyPath(cfg.initial_path) ? cfg.initial_path : null;
+ if (cfg.type === 'remote' && !isEmpty(cfg.initial_path)) {
+ /* initial_path is read off the local disk as root; same whitelist
+ * as the local ruleset path - and now the same fail-loud
+ * treatment. Dropping it silently made a rule-set the user
+ * believed had a local copy depend on a download at first start,
+ * with nothing said about why. */
+ if (!validateHomeProxyPath(cfg.initial_path))
+ die(sprintf("homeproxy-pro: rule-set '%s' initial_path '%s' is outside the homeproxy-pro whitelist; choose a path under /etc/homeproxy-pro/.", cfg.name, cfg.initial_path));
+ ruleset.initial_path = cfg.initial_path;
+ }
push(rule_set_array, ruleset);
}
};
@@ -114,8 +132,21 @@ export function build_http_clients(rule_set_array, dm, ctx) {
if (rs.type !== 'remote')
continue;
- if (isEmpty(detour))
- detour = (ctx.routing_mode === 'custom') ? (get_outbound(ctx.default_outbound, dm) || 'direct-out') : 'direct-out';
+ if (isEmpty(detour)) {
+ /* A remote rule-set still has to be fetched through something.
+ * The preset modes used to fall back to direct-out, which fails
+ * on a router that can only reach GitHub through the proxy - and
+ * the only trace is a line in the sing-box log. Follow the
+ * mode's main outbound instead, the way the built-in geoip-cn /
+ * geosite-cn rule-sets already do (route.uc gives them
+ * download_detour: main-out). */
+ if (ctx.routing_mode === 'custom')
+ detour = get_outbound(ctx.default_outbound, dm) || 'direct-out';
+ else if (!isEmpty(ctx.main_node))
+ detour = 'main-out';
+ else
+ detour = 'direct-out';
+ }
const tag = 'hp-' + detour;
rs.http_client = tag;
diff --git a/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/runtime/config.sh b/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/runtime/config.sh
index 2f5ec7ce9..6ee5e7827 100644
--- a/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/runtime/config.sh
+++ b/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/runtime/config.sh
@@ -149,3 +149,29 @@ hp_restore_known_good_uci() {
[ "$saved" = "1" ] && uci commit homeproxy-pro
+# Digest of , or nothing when it is missing or empty. The reload path
+# uses it to tell "the generator produced the same bytes as what is running"
+# from "something actually changed".
+hp_file_digest() {
+ [ -s "$1" ] || return 1
+
+ md5sum "$1" 2>"/dev/null" | awk '{ print $1 }'
+}
+
+# hp_config_changed
+# 0 when differs from (or does not exist), 1 when the
+# two are byte-identical. An empty means there was nothing
+# running to compare against, which counts as a change.
+hp_config_changed() {
+ local live="$1"
+ local before="$2"
+ local now
+
+ now="$(hp_file_digest "$live")" || return 0
+
+ [ -n "$before" ] && [ "$now" = "$before" ] && return 1
+
+ return 0
+}
diff --git a/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/runtime/dns.sh b/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/runtime/dns.sh
index 0b7084202..19824eb39 100644
--- a/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/runtime/dns.sh
+++ b/luci-app-homeproxy-pro/root/etc/homeproxy-pro/scripts/runtime/dns.sh
@@ -115,7 +115,19 @@ hp_dnsmasq_render_snippets() {
hp_dnsmasq_has_nftset || nftset_ok=0
case "$routing_mode" in
- "bypass_mainland_china"|"custom"|"global")
+ "bypass_mainland_china"|"custom"|"global"|"proxy_mainland_china")
+ # Every one of these hands all queries to sing-box, which is the only
+ # DNS front end the mode needs.
+ #
+ # proxy_mainland_china used to be the exception: it expanded
+ # china_list.txt (111353 domains in the bundled list) into one
+ # `server=` line each, so that only those domains reached sing-box.
+ # Measured on a real device (ImmortalWrt 25.12.2, x86-64, dnsmasq
+ # 2.93) that costs about 25 MB of RSS - 1.0 MB empty, 27.2 MB with
+ # the list - while the outcome is the same: this mode's dns.final is
+ # default-dns (the WAN resolver), so a mainland domain resolves
+ # through the ISP either way. The split that actually matters is
+ # decided on the route side, by geoip-cn/china-ip.
cat <<-EOF > "$stage/redirect-dns.conf" || return 1
no-poll
no-resolv
@@ -135,12 +147,6 @@ hp_dnsmasq_render_snippets() {
fi
fi
;;
- "proxy_mainland_china")
- if [ -s "$hp_dir/resources/china_list.txt" ]; then
- sed -r -e "s/(.*)/server=\/\1\/127.0.0.1#$dns_port/g" \
- "$hp_dir/resources/china_list.txt" > "$stage/china_list.conf" || return 1
- fi
- ;;
esac
if [ "$routing_mode" != "custom" ] && [ -s "$hp_dir/resources/proxy_list.txt" ]; then
diff --git a/luci-app-homeproxy-pro/root/etc/init.d/homeproxy-pro b/luci-app-homeproxy-pro/root/etc/init.d/homeproxy-pro
index 401367aed..883684875 100755
--- a/luci-app-homeproxy-pro/root/etc/init.d/homeproxy-pro
+++ b/luci-app-homeproxy-pro/root/etc/init.d/homeproxy-pro
@@ -515,6 +515,21 @@ reload_service() {
HP_CANDIDATE_CLIENT=0
HP_CANDIDATE_SERVER=0
+ # Digest what is running *before* the generators overwrite it: a reload
+ # that produces the same bytes must not stop and start the instances.
+ # service_triggers() reloads on every `interface.*.up` for wan, and a
+ # flapping PPPoE link would otherwise interrupt every connection even when
+ # the WAN resolver - the only thing that trigger can change - came back
+ # the same.
+ local digest_c="" digest_s="" server_enabled
+ if [ "$outbound_node" != "nil" ]; then
+ digest_c="$(hp_file_digest "$RUN_DIR/sing-box-c.json")"
+ fi
+ config_get_bool server_enabled "server" "enabled" "0"
+ if [ "$server_enabled" = "1" ]; then
+ digest_s="$(hp_file_digest "$RUN_DIR/sing-box-s.json")"
+ fi
+
if [ "$outbound_node" != "nil" ]; then
if ! ucode -S "$HP_DIR/scripts/generate_client.uc" 2>>"$LOG_PATH"; then
log "Error: new client configuration is invalid, reload aborted; the running configuration is kept."
@@ -523,8 +538,6 @@ reload_service() {
HP_CANDIDATE_CLIENT=1
fi
- local server_enabled
- config_get_bool server_enabled "server" "enabled" "0"
if [ "$server_enabled" = "1" ]; then
if ! ucode -S "$HP_DIR/scripts/generate_server.uc" 2>>"$LOG_PATH"; then
log "Error: new server configuration is invalid, reload aborted; the running configuration is kept."
@@ -533,6 +546,23 @@ reload_service() {
HP_CANDIDATE_SERVER=1
fi
+ # Both sides regenerated to what is already live: keep them running. The
+ # staged candidates are dropped too, so a later start that never validated
+ # anything cannot pick them up.
+ local reload_needed=0
+ if [ "$outbound_node" != "nil" ] && hp_config_changed "$RUN_DIR/sing-box-c.json" "$digest_c"; then
+ reload_needed=1
+ fi
+ if [ "$server_enabled" = "1" ] && hp_config_changed "$RUN_DIR/sing-box-s.json" "$digest_s"; then
+ reload_needed=1
+ fi
+
+ if [ "$reload_needed" = "0" ]; then
+ rm -rf "$RUN_DIR/candidate"
+ log "Reload: the generated configuration is unchanged; the running instances are kept."
+ return 0
+ fi
+
# stop_service() deletes $RUN_DIR/sing-box-*.json (runtime state), so the
# file that was just validated would not survive to start_service. Stage a
# copy of each validated side first; the stop that follows cannot reach it,
diff --git a/luci-app-homeproxy-pro/root/usr/share/rpcd/acl.d/luci-app-homeproxy-pro.json b/luci-app-homeproxy-pro/root/usr/share/rpcd/acl.d/luci-app-homeproxy-pro.json
index 4b9edac6f..cecb61225 100644
--- a/luci-app-homeproxy-pro/root/usr/share/rpcd/acl.d/luci-app-homeproxy-pro.json
+++ b/luci-app-homeproxy-pro/root/usr/share/rpcd/acl.d/luci-app-homeproxy-pro.json
@@ -12,6 +12,7 @@
"luci.homeproxy-pro": [
"acllist_read",
"connection_check",
+ "gfw_set_counts",
"node_parse",
"resources_get_version",
"singbox_get_features",
diff --git a/luci-app-homeproxy-pro/root/usr/share/rpcd/ucode/luci.homeproxy-pro b/luci-app-homeproxy-pro/root/usr/share/rpcd/ucode/luci.homeproxy-pro
index c0896a492..ea46dcbe1 100644
--- a/luci-app-homeproxy-pro/root/usr/share/rpcd/ucode/luci.homeproxy-pro
+++ b/luci-app-homeproxy-pro/root/usr/share/rpcd/ucode/luci.homeproxy-pro
@@ -18,6 +18,11 @@ function hasKernelModule(kmod) {
return (system(sprintf('[ -e "/lib/modules/$(uname -r)"/%s ]', shellQuote(kmod))) === 0);
}
+/* The nft sets gfw_set_counts() reports on. A named constant rather than an
+ * `in [...]` literal: this file keeps its membership tests on index() and
+ * named arrays (arch-guard checks both). */
+const GFW_SET_FAMILIES = ['v4', 'v6'];
+
const HP_DIR = '/etc/homeproxy-pro';
const RUN_DIR = '/var/run/homeproxy-pro';
const LOG_PATH = `${RUN_DIR}/homeproxy-pro.log`;
@@ -51,6 +56,37 @@ function collectFeatures() {
return features;
}
+/* Element count of an nft set, or null when the set does not exist. Used by
+ * gfw_set_counts(): the gfwlist routing mode decides with a set that dnsmasq
+ * fills through `nftset=`, so an empty set means the mode is silently routing
+ * everything direct. */
+function nft_set_size(name) {
+ const ret = executeCommand('nft', '-j', 'list', 'set', 'inet', 'fw4', shellQuote(name));
+
+ if (ret == null || ret.exitcode !== 0 || isEmpty(ret.stdout))
+ return null;
+
+ let doc;
+ try {
+ doc = json(ret.stdout);
+ } catch (e) {
+ return null;
+ }
+
+ /* `elem` is what nft 1.1.6 emits (`nft -j list set`); older builds used
+ * `elements`. Accepting both keeps the count right on either, because
+ * the wrong key silently reads as "0 elements" - the very answer this
+ * method exists to distinguish from "not filled". */
+ for (let item in (doc?.nftables || [])) {
+ const elements = item.set ? (item.set.elem || item.set.elements) : null;
+
+ if (elements)
+ return length(elements);
+ }
+
+ return 0;
+}
+
const methods = {
acllist_read: {
args: { type: 'type' },
@@ -436,6 +472,23 @@ const methods = {
return { running, log_tail: tail };
}
+ },
+
+ /* The gfwlist mode routes by an nft set that dnsmasq populates through
+ * `nftset=`. A LAN client with its own DoH/DoT never asks this router's
+ * DNS, the set stays empty, and the mode degrades to direct routing with
+ * no visible symptom - the count is what separates "the mode is working"
+ * from "the mode is doing nothing". */
+ gfw_set_counts: {
+ args: {},
+ call: function() {
+ let counts = {};
+
+ for (let family in GFW_SET_FAMILIES)
+ counts[family] = nft_set_size('homeproxy_gfw_list_' + family);
+
+ return counts;
+ }
}
};
diff --git a/luci-app-homeproxy-pro/tests/fixtures/generators/dns_action_guards.uci b/luci-app-homeproxy-pro/tests/fixtures/generators/dns_action_guards.uci
new file mode 100644
index 000000000..abfcb0123
--- /dev/null
+++ b/luci-app-homeproxy-pro/tests/fixtures/generators/dns_action_guards.uci
@@ -0,0 +1,51 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# DNS rule action fields that sing-box validates at load time
+# (route/rule/rule_dns.go, validateDNSRuleAction):
+#
+# * `race` and `speculative` cannot be combined on the same rule;
+# * `client_subnet` and `remove_client_subnet` are mutually exclusive.
+#
+# The rules below are a *legal* baseline: both sides of each pair are off.
+# The testbed opens one side with a one-line sed variation, so the case that
+# must be refused differs from the case that must pass by exactly the flag
+# under test.
+
+config homeproxy-pro 'config'
+ option routing_mode 'custom'
+ option proxy_mode 'redirect_tproxy'
+ option ipv6_support '1'
+ option log_level 'error'
+
+config homeproxy-pro 'routing'
+ option default_outbound 'direct-out'
+ option default_outbound_dns 'default-dns'
+
+config homeproxy-pro 'infra'
+ option mixed_port '5330'
+ option dns_port '5333'
+ option redirect_port '5331'
+ option self_mark '100'
+
+config dns_rule 'dr_race'
+ option enabled '1'
+ option label 'dr-race'
+ option action 'route'
+ option server 'default-dns'
+ option race '0'
+ option speculative '0'
+
+config dns_rule 'dr_subnet'
+ option enabled '1'
+ option label 'dr-subnet'
+ option action 'route'
+ option server 'default-dns'
+ option client_subnet '1.2.3.0/24'
+ option remove_client_subnet '0'
+
+config dns_rule 'dr_eval'
+ option enabled '1'
+ option label 'dr-eval'
+ option action 'evaluate'
+ option server 'default-dns'
+ option dns_timeout '5'
diff --git a/luci-app-homeproxy-pro/tests/fixtures/generators/dns_headers.uci b/luci-app-homeproxy-pro/tests/fixtures/generators/dns_headers.uci
new file mode 100644
index 000000000..cbff936ed
--- /dev/null
+++ b/luci-app-homeproxy-pro/tests/fixtures/generators/dns_headers.uci
@@ -0,0 +1,33 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# A DNS server carrying HTTP headers. UCI stores them as a string list
+# ("Name: Value" per entry), sing-box wants map[string][]string, and emitting
+# the UCI list verbatim makes sing-box refuse the whole configuration
+# ("cannot unmarshal array into Go value of type badoption.HTTPHeader").
+# The generator converts one shape into the other; a name that appears twice
+# becomes a two-value header.
+
+config homeproxy-pro 'config'
+ option routing_mode 'custom'
+ option proxy_mode 'redirect_tproxy'
+ option ipv6_support '1'
+ option log_level 'error'
+
+config homeproxy-pro 'routing'
+ option default_outbound 'direct-out'
+ option default_outbound_dns 'default-dns'
+
+config homeproxy-pro 'infra'
+ option mixed_port '5330'
+ option dns_port '5333'
+ option redirect_port '5331'
+ option self_mark '100'
+
+config dns_server 'ds_https'
+ option enabled '1'
+ option type 'https'
+ option server '1.1.1.1'
+ option path '/dns-query'
+ list headers 'X-Test: one'
+ list headers 'X-Test: two'
+ list headers 'Authorization: Bearer t'
diff --git a/luci-app-homeproxy-pro/tests/fixtures/generators/routing_rule_disabled_node.uci b/luci-app-homeproxy-pro/tests/fixtures/generators/routing_rule_disabled_node.uci
new file mode 100644
index 000000000..8d96cfcb3
--- /dev/null
+++ b/luci-app-homeproxy-pro/tests/fixtures/generators/routing_rule_disabled_node.uci
@@ -0,0 +1,39 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# A routing rule pointing at a routing_node that was switched off afterwards.
+# generate_outbound() skips disabled nodes, so the tag the rule refers to is
+# generated nowhere and sing-box refuses the whole configuration with
+# "outbound not found: cfg-rn_off-out" - a message that never names the node
+# the user turned off. get_outbound() now refuses it the way
+# get_resolver()/get_ruleset() already do.
+
+config homeproxy-pro 'config'
+ option routing_mode 'custom'
+ option proxy_mode 'redirect_tproxy'
+ option ipv6_support '1'
+ option log_level 'error'
+
+config homeproxy-pro 'routing'
+ option default_outbound 'direct-out'
+ option default_outbound_dns 'default-dns'
+
+config homeproxy-pro 'infra'
+ option mixed_port '5330'
+ option dns_port '5333'
+ option redirect_port '5331'
+ option self_mark '100'
+
+config node 'n_direct'
+ option label 'n-direct'
+ option type 'direct'
+
+config routing_node 'rn_off'
+ option enabled '0'
+ option label 'rn-off'
+ option node 'n_direct'
+
+config routing_rule 'rr_off'
+ option enabled '1'
+ option label 'rr-off'
+ option action 'route'
+ option outbound 'rn_off'
diff --git a/luci-app-homeproxy-pro/tests/fixtures/generators/routing_rule_route_options_empty.uci b/luci-app-homeproxy-pro/tests/fixtures/generators/routing_rule_route_options_empty.uci
new file mode 100644
index 000000000..0782ce00b
--- /dev/null
+++ b/luci-app-homeproxy-pro/tests/fixtures/generators/routing_rule_route_options_empty.uci
@@ -0,0 +1,29 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# A routing rule on the action the UI offers but gives no field to fill.
+# Match fields belong to the rule, not to the action payload, so this rule
+# reaches sing-box as `{"action": "route-options", ...}` with an empty action
+# object, and RouteOptionsActionOptions.UnmarshalJSON() refuses exactly that
+# with "empty route option action" - taking the whole configuration with it.
+# The generator refuses it by name before writing anything.
+
+config homeproxy-pro 'config'
+ option routing_mode 'custom'
+ option proxy_mode 'redirect_tproxy'
+ option ipv6_support '1'
+ option log_level 'error'
+
+config homeproxy-pro 'routing'
+ option default_outbound 'direct-out'
+ option default_outbound_dns 'default-dns'
+
+config homeproxy-pro 'infra'
+ option mixed_port '5330'
+ option dns_port '5333'
+ option redirect_port '5331'
+ option self_mark '100'
+
+config routing_rule 'rr_empty'
+ option enabled '1'
+ option label 'rr-empty'
+ option action 'route-options'
diff --git a/luci-app-homeproxy-pro/tests/fixtures/generators/routing_rule_tls_fragment.uci b/luci-app-homeproxy-pro/tests/fixtures/generators/routing_rule_tls_fragment.uci
new file mode 100644
index 000000000..10c7ef2f4
--- /dev/null
+++ b/luci-app-homeproxy-pro/tests/fixtures/generators/routing_rule_tls_fragment.uci
@@ -0,0 +1,32 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Both TLS fragmentation switches at once. RouteOptionsActionOptions's
+# UnmarshalJSON() treats them as mutually exclusive
+# ("`tls_fragment` and `tls_record_fragment` are mutually exclusive"), and
+# the form shows both switches without hiding either when the other is on -
+# a user following the description ("try TLS record fragment first") can
+# easily tick both.
+
+config homeproxy-pro 'config'
+ option routing_mode 'custom'
+ option proxy_mode 'redirect_tproxy'
+ option ipv6_support '1'
+ option log_level 'error'
+
+config homeproxy-pro 'routing'
+ option default_outbound 'direct-out'
+ option default_outbound_dns 'default-dns'
+
+config homeproxy-pro 'infra'
+ option mixed_port '5330'
+ option dns_port '5333'
+ option redirect_port '5331'
+ option self_mark '100'
+
+config routing_rule 'rr_tls'
+ option enabled '1'
+ option label 'rr-tls'
+ option action 'route'
+ option outbound 'direct-out'
+ option tls_fragment '1'
+ option tls_record_fragment '1'
diff --git a/luci-app-homeproxy-pro/tests/fixtures/generators/ruleset_remote_bad_initial_path.uci b/luci-app-homeproxy-pro/tests/fixtures/generators/ruleset_remote_bad_initial_path.uci
new file mode 100644
index 000000000..5da314be7
--- /dev/null
+++ b/luci-app-homeproxy-pro/tests/fixtures/generators/ruleset_remote_bad_initial_path.uci
@@ -0,0 +1,38 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# A remote rule-set whose initial_path points outside the homeproxy-pro whitelist.
+# The path is read off the local disk as root, so the same gate as a local
+# rule-set's path applies - and it has to fail the same way. It used to be
+# dropped silently (initial_path := null), which left a rule-set the user
+# believed had a local copy downloading at first start instead.
+
+config homeproxy-pro 'config'
+ option routing_mode 'custom'
+ option proxy_mode 'redirect_tproxy'
+ option ipv6_support '1'
+ option log_level 'error'
+
+config homeproxy-pro 'routing'
+ option default_outbound 'direct-out'
+ option default_outbound_dns 'default-dns'
+
+config homeproxy-pro 'infra'
+ option mixed_port '5330'
+ option dns_port '5333'
+ option redirect_port '5331'
+ option self_mark '100'
+
+config ruleset 'rs_seed'
+ option enabled '1'
+ option label 'rs-seed'
+ option type 'remote'
+ option format 'binary'
+ option url 'https://example.invalid/ruleset.srs'
+ option initial_path '/etc/passwd'
+
+config routing_rule 'rr_seed'
+ option enabled '1'
+ option label 'rr-seed'
+ option action 'route'
+ option outbound 'direct-out'
+ option rule_set 'rs_seed'
diff --git a/luci-app-homeproxy-pro/tests/fixtures/generators/ruleset_remote_preset.uci b/luci-app-homeproxy-pro/tests/fixtures/generators/ruleset_remote_preset.uci
new file mode 100644
index 000000000..26203ed6c
--- /dev/null
+++ b/luci-app-homeproxy-pro/tests/fixtures/generators/ruleset_remote_preset.uci
@@ -0,0 +1,51 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# A remote rule-set in a preset routing mode. It used to be fetched through
+# direct-out regardless of the mode, which fails on a router that can only
+# reach the rule-set host through the proxy - and the only trace is a line in
+# the sing-box log. The built-in geoip-cn / geosite-cn rule-sets already
+# download through main-out (route.uc sets download_detour for them).
+
+config homeproxy-pro 'config'
+ option routing_mode 'bypass_mainland_china'
+ option proxy_mode 'redirect_tproxy'
+ option ipv6_support '1'
+ option log_level 'error'
+ option main_node 'n_vless'
+
+config homeproxy-pro 'routing'
+ option default_outbound 'direct-out'
+
+config homeproxy-pro 'infra'
+ option mixed_port '5330'
+ option dns_port '5333'
+ option redirect_port '5331'
+ option self_mark '100'
+
+config node 'n_vless'
+ option label 'n-vless'
+ option type 'vless'
+ option address 'example.invalid'
+ option port '443'
+ option uuid '00000000-0000-0000-0000-000000000000'
+ option tls '1'
+ option tls_sni 'example.invalid'
+
+config routing_node 'rn_main'
+ option enabled '1'
+ option label 'rn-main'
+ option node 'n_vless'
+
+config ruleset 'rs_remote'
+ option enabled '1'
+ option label 'rs-remote'
+ option type 'remote'
+ option format 'binary'
+ option url 'https://example.invalid/ruleset.srs'
+
+config routing_rule 'rr_rs'
+ option enabled '1'
+ option label 'rr-rs'
+ option action 'route'
+ option outbound 'rn_main'
+ option rule_set 'rs_remote'
diff --git a/luci-app-homeproxy-pro/tests/fixtures/generators/ruleset_remote_update_interval.uci b/luci-app-homeproxy-pro/tests/fixtures/generators/ruleset_remote_update_interval.uci
new file mode 100644
index 000000000..5938da360
--- /dev/null
+++ b/luci-app-homeproxy-pro/tests/fixtures/generators/ruleset_remote_update_interval.uci
@@ -0,0 +1,38 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# A remote rule-set whose update_interval is a bare number. sing-box parses
+# the field as a duration and refuses a number without a unit
+# ("time: missing unit in duration"), which rejects the whole configuration -
+# the same failure the DNS timeouts are normalised for. The form only offers
+# a placeholder ("1d"), not a format check.
+
+config homeproxy-pro 'config'
+ option routing_mode 'custom'
+ option proxy_mode 'redirect_tproxy'
+ option ipv6_support '1'
+ option log_level 'error'
+
+config homeproxy-pro 'routing'
+ option default_outbound 'direct-out'
+ option default_outbound_dns 'default-dns'
+
+config homeproxy-pro 'infra'
+ option mixed_port '5330'
+ option dns_port '5333'
+ option redirect_port '5331'
+ option self_mark '100'
+
+config ruleset 'rs_remote'
+ option enabled '1'
+ option label 'rs-remote'
+ option type 'remote'
+ option format 'binary'
+ option url 'https://example.invalid/ruleset.srs'
+ option update_interval '3600'
+
+config routing_rule 'rr_rs'
+ option enabled '1'
+ option label 'rr-rs'
+ option action 'route'
+ option outbound 'direct-out'
+ option rule_set 'rs_remote'
diff --git a/luci-app-homeproxy-pro/tests/fixtures/runtime/trace.golden.txt b/luci-app-homeproxy-pro/tests/fixtures/runtime/trace.golden.txt
index 29d7705aa..75421c238 100644
--- a/luci-app-homeproxy-pro/tests/fixtures/runtime/trace.golden.txt
+++ b/luci-app-homeproxy-pro/tests/fixtures/runtime/trace.golden.txt
@@ -109,6 +109,8 @@ nft flush chain inet fw4 homeproxy_mangle_tun
nft delete chain inet fw4 homeproxy_mangle_tun
nft flush chain inet fw4 homeproxy_mangle_tun_mark
nft delete chain inet fw4 homeproxy_mangle_tun_mark
+nft flush chain inet fw4 homeproxy_ipv6_guard
+nft delete chain inet fw4 homeproxy_ipv6_guard
nft flush set inet fw4 homeproxy_local_addr_v4
nft delete set inet fw4 homeproxy_local_addr_v4
nft flush set inet fw4 homeproxy_local_addr_v6
@@ -301,6 +303,8 @@ nft flush chain inet fw4 homeproxy_mangle_tun
nft delete chain inet fw4 homeproxy_mangle_tun
nft flush chain inet fw4 homeproxy_mangle_tun_mark
nft delete chain inet fw4 homeproxy_mangle_tun_mark
+nft flush chain inet fw4 homeproxy_ipv6_guard
+nft delete chain inet fw4 homeproxy_ipv6_guard
nft flush set inet fw4 homeproxy_local_addr_v4
nft delete set inet fw4 homeproxy_local_addr_v4
nft flush set inet fw4 homeproxy_local_addr_v6
@@ -385,6 +389,8 @@ nft flush chain inet fw4 homeproxy_mangle_tun
nft delete chain inet fw4 homeproxy_mangle_tun
nft flush chain inet fw4 homeproxy_mangle_tun_mark
nft delete chain inet fw4 homeproxy_mangle_tun_mark
+nft flush chain inet fw4 homeproxy_ipv6_guard
+nft delete chain inet fw4 homeproxy_ipv6_guard
nft flush set inet fw4 homeproxy_local_addr_v4
nft delete set inet fw4 homeproxy_local_addr_v4
nft flush set inet fw4 homeproxy_local_addr_v6
@@ -526,6 +532,8 @@ nft flush chain inet fw4 homeproxy_mangle_tun
nft delete chain inet fw4 homeproxy_mangle_tun
nft flush chain inet fw4 homeproxy_mangle_tun_mark
nft delete chain inet fw4 homeproxy_mangle_tun_mark
+nft flush chain inet fw4 homeproxy_ipv6_guard
+nft delete chain inet fw4 homeproxy_ipv6_guard
nft flush set inet fw4 homeproxy_local_addr_v4
nft delete set inet fw4 homeproxy_local_addr_v4
nft flush set inet fw4 homeproxy_local_addr_v6
@@ -729,6 +737,8 @@ nft flush chain inet fw4 homeproxy_mangle_tun
nft delete chain inet fw4 homeproxy_mangle_tun
nft flush chain inet fw4 homeproxy_mangle_tun_mark
nft delete chain inet fw4 homeproxy_mangle_tun_mark
+nft flush chain inet fw4 homeproxy_ipv6_guard
+nft delete chain inet fw4 homeproxy_ipv6_guard
nft flush set inet fw4 homeproxy_local_addr_v4
nft delete set inet fw4 homeproxy_local_addr_v4
nft flush set inet fw4 homeproxy_local_addr_v6
@@ -827,6 +837,8 @@ nft flush chain inet fw4 homeproxy_mangle_tun
nft delete chain inet fw4 homeproxy_mangle_tun
nft flush chain inet fw4 homeproxy_mangle_tun_mark
nft delete chain inet fw4 homeproxy_mangle_tun_mark
+nft flush chain inet fw4 homeproxy_ipv6_guard
+nft delete chain inet fw4 homeproxy_ipv6_guard
nft flush set inet fw4 homeproxy_local_addr_v4
nft delete set inet fw4 homeproxy_local_addr_v4
nft flush set inet fw4 homeproxy_local_addr_v6
@@ -969,6 +981,8 @@ nft flush chain inet fw4 homeproxy_mangle_tun
nft delete chain inet fw4 homeproxy_mangle_tun
nft flush chain inet fw4 homeproxy_mangle_tun_mark
nft delete chain inet fw4 homeproxy_mangle_tun_mark
+nft flush chain inet fw4 homeproxy_ipv6_guard
+nft delete chain inet fw4 homeproxy_ipv6_guard
nft flush set inet fw4 homeproxy_local_addr_v4
nft delete set inet fw4 homeproxy_local_addr_v4
nft flush set inet fw4 homeproxy_local_addr_v6
@@ -1120,6 +1134,8 @@ nft flush chain inet fw4 homeproxy_mangle_tun
nft delete chain inet fw4 homeproxy_mangle_tun
nft flush chain inet fw4 homeproxy_mangle_tun_mark
nft delete chain inet fw4 homeproxy_mangle_tun_mark
+nft flush chain inet fw4 homeproxy_ipv6_guard
+nft delete chain inet fw4 homeproxy_ipv6_guard
nft flush set inet fw4 homeproxy_local_addr_v4
nft delete set inet fw4 homeproxy_local_addr_v4
nft flush set inet fw4 homeproxy_local_addr_v6
@@ -1204,6 +1220,8 @@ nft flush chain inet fw4 homeproxy_mangle_tun
nft delete chain inet fw4 homeproxy_mangle_tun
nft flush chain inet fw4 homeproxy_mangle_tun_mark
nft delete chain inet fw4 homeproxy_mangle_tun_mark
+nft flush chain inet fw4 homeproxy_ipv6_guard
+nft delete chain inet fw4 homeproxy_ipv6_guard
nft flush set inet fw4 homeproxy_local_addr_v4
nft delete set inet fw4 homeproxy_local_addr_v4
nft flush set inet fw4 homeproxy_local_addr_v6
@@ -1411,6 +1429,8 @@ nft flush chain inet fw4 homeproxy_mangle_tun
nft delete chain inet fw4 homeproxy_mangle_tun
nft flush chain inet fw4 homeproxy_mangle_tun_mark
nft delete chain inet fw4 homeproxy_mangle_tun_mark
+nft flush chain inet fw4 homeproxy_ipv6_guard
+nft delete chain inet fw4 homeproxy_ipv6_guard
nft flush set inet fw4 homeproxy_local_addr_v4
nft delete set inet fw4 homeproxy_local_addr_v4
nft flush set inet fw4 homeproxy_local_addr_v6
@@ -1476,6 +1496,8 @@ nft flush chain inet fw4 homeproxy_mangle_tun
nft delete chain inet fw4 homeproxy_mangle_tun
nft flush chain inet fw4 homeproxy_mangle_tun_mark
nft delete chain inet fw4 homeproxy_mangle_tun_mark
+nft flush chain inet fw4 homeproxy_ipv6_guard
+nft delete chain inet fw4 homeproxy_ipv6_guard
nft flush set inet fw4 homeproxy_local_addr_v4
nft delete set inet fw4 homeproxy_local_addr_v4
nft flush set inet fw4 homeproxy_local_addr_v6
@@ -1623,6 +1645,8 @@ nft flush chain inet fw4 homeproxy_mangle_tun
nft delete chain inet fw4 homeproxy_mangle_tun
nft flush chain inet fw4 homeproxy_mangle_tun_mark
nft delete chain inet fw4 homeproxy_mangle_tun_mark
+nft flush chain inet fw4 homeproxy_ipv6_guard
+nft delete chain inet fw4 homeproxy_ipv6_guard
nft flush set inet fw4 homeproxy_local_addr_v4
nft delete set inet fw4 homeproxy_local_addr_v4
nft flush set inet fw4 homeproxy_local_addr_v6
@@ -1805,6 +1829,8 @@ nft flush chain inet fw4 homeproxy_mangle_tun
nft delete chain inet fw4 homeproxy_mangle_tun
nft flush chain inet fw4 homeproxy_mangle_tun_mark
nft delete chain inet fw4 homeproxy_mangle_tun_mark
+nft flush chain inet fw4 homeproxy_ipv6_guard
+nft delete chain inet fw4 homeproxy_ipv6_guard
nft flush set inet fw4 homeproxy_local_addr_v4
nft delete set inet fw4 homeproxy_local_addr_v4
nft flush set inet fw4 homeproxy_local_addr_v6
@@ -1926,6 +1952,8 @@ nft flush chain inet fw4 homeproxy_mangle_tun
nft delete chain inet fw4 homeproxy_mangle_tun
nft flush chain inet fw4 homeproxy_mangle_tun_mark
nft delete chain inet fw4 homeproxy_mangle_tun_mark
+nft flush chain inet fw4 homeproxy_ipv6_guard
+nft delete chain inet fw4 homeproxy_ipv6_guard
nft flush set inet fw4 homeproxy_local_addr_v4
nft delete set inet fw4 homeproxy_local_addr_v4
nft flush set inet fw4 homeproxy_local_addr_v6
diff --git a/luci-app-homeproxy-pro/tests/frontend-validators.js b/luci-app-homeproxy-pro/tests/frontend-validators.js
index 693a19c30..ee4f72865 100644
--- a/luci-app-homeproxy-pro/tests/frontend-validators.js
+++ b/luci-app-homeproxy-pro/tests/frontend-validators.js
@@ -133,5 +133,35 @@ check('cert path ignores an empty value',
check('cert path ignores a missing section',
hp.validateCertificatePath(null, '/etc/passwd') === true);
+/* A query type name reaches sing-box's miekg/dns uppercase table
+ * (option/types.go); a lowercase name is an "unknown DNS query type" and the
+ * whole configuration is refused, which the user only ever saw as the reload
+ * keeping the previous state. A numeric code is valid as-is. */
+check('query type accepts an uppercase name',
+ hp.validateQueryType('sec1', 'AAAA') === true);
+check('query type accepts a numeric code',
+ hp.validateQueryType('sec1', '65') === true);
+check('query type rejects a lowercase name',
+ isError(hp.validateQueryType('sec1', 'aaaa')));
+check('query type rejects a name with a trailing space',
+ isError(hp.validateQueryType('sec1', 'A ')));
+check('query type rejects a code above 65535',
+ isError(hp.validateQueryType('sec1', '70000')));
+check('query type ignores an empty value',
+ hp.validateQueryType('sec1', '') === true);
+check('query type ignores a missing section',
+ hp.validateQueryType(null, 'aaaa') === true);
+
+/* An uncompilable regex is not caught anywhere downstream either: sing-box
+ * rejects the configuration as a whole. */
+check('regex accepts a valid pattern',
+ hp.validateRegex('sec1', '^stun\\..+') === true);
+check('regex rejects an unbalanced group',
+ isError(hp.validateRegex('sec1', '(unclosed')));
+check('regex ignores an empty value',
+ hp.validateRegex('sec1', '') === true);
+check('regex ignores a missing section',
+ hp.validateRegex(null, '(unclosed') === true);
+
console.log(`frontend validators: ${checks} checks, ${failures} failures`);
process.exit(failures ? 1 : 0);
diff --git a/luci-app-homeproxy-pro/tests/runtime/test_config_transaction.sh b/luci-app-homeproxy-pro/tests/runtime/test_config_transaction.sh
index c88eafcc5..f7e14f152 100644
--- a/luci-app-homeproxy-pro/tests/runtime/test_config_transaction.sh
+++ b/luci-app-homeproxy-pro/tests/runtime/test_config_transaction.sh
@@ -378,5 +378,29 @@ reset_samples
hp_wait_instance "sing-box-c" "$CFG" 1
expect "wait_instance (compat): times out when down" "$?" "1"
+# A reload that regenerates identical bytes must not restart the instances:
+# service_triggers() reloads on every `interface.*.up` for wan, so a flapping
+# link used to interrupt every connection for a configuration that had not
+# changed. hp_config_changed() is the decision, and it is pure shell.
+printf 'configuration one\n' > "$WORK/live.json"
+DIGEST="$(hp_file_digest "$WORK/live.json")"
+expect "digest is stable" "$(hp_file_digest "$WORK/live.json")" "$DIGEST"
+
+hp_config_changed "$WORK/live.json" "$DIGEST" && RC=1 || RC=0
+expect "an identical file is not a change" "$RC" "0"
+
+printf 'configuration two\n' > "$WORK/live.json"
+hp_config_changed "$WORK/live.json" "$DIGEST" && RC=1 || RC=0
+expect "a rewritten file is a change" "$RC" "1"
+
+hp_config_changed "$WORK/missing.json" "$DIGEST" && RC=1 || RC=0
+expect "a missing live file is a change" "$RC" "1"
+
+hp_config_changed "$WORK/live.json" "" && RC=1 || RC=0
+expect "nothing to compare against is a change" "$RC" "1"
+
+: > "$WORK/empty.json"
+expect "an empty file has no digest" "$(hp_file_digest "$WORK/empty.json")" ""
+
printf '%d checks, %d failures\n' "$CHECKS" "$FAILURES"
exit $FAILED
diff --git a/luci-app-homeproxy-pro/tests/runtime/test_dns_snippets.sh b/luci-app-homeproxy-pro/tests/runtime/test_dns_snippets.sh
index 655027569..1d4a54adf 100755
--- a/luci-app-homeproxy-pro/tests/runtime/test_dns_snippets.sh
+++ b/luci-app-homeproxy-pro/tests/runtime/test_dns_snippets.sh
@@ -242,6 +242,22 @@ expect "an unusable dns_port does not restart dnsmasq" "$(restart_count)" "$GOOD
expect "an unusable dns_port is reported in the log" \
"$(grep -c 'dns_port' "$LOG")" "1"
+echo
+echo "== proxy_mainland_china: every query goes to sing-box =="
+
+# This mode used to expand china_list.txt (111353 domains in the bundled list)
+# into one `server=` line each, which measured at about 25 MB of dnsmasq RSS on
+# a real device (1.0 MB empty, 27.2 MB with the list) for no behavioural
+# difference: the mode's dns.final is default-dns, the WAN resolver, so a
+# mainland domain resolves through the ISP either way.
+run "proxy_mainland_china"
+expect "proxy_mainland_china installs the global redirect snippet" \
+ "$(exists "$DNS_DIR/redirect-dns.conf")" "yes"
+expect "proxy_mainland_china no longer expands china_list.txt" \
+ "$(exists "$DNS_DIR/china_list.conf")" "no"
+expect "the redirect snippet points at sing-box" \
+ "$(grep -c "server=127.0.0.1#" "$DNS_DIR/redirect-dns.conf")" "1"
+
echo
printf '%s checks, %s failures\n' "$CHECKS" "$FAILURES"
if [ "$FAILED" != 0 ]; then
diff --git a/luci-app-homeproxy-pro/tests/snapshots/client.json b/luci-app-homeproxy-pro/tests/snapshots/client.json
index 96177f007..b29853695 100644
--- a/luci-app-homeproxy-pro/tests/snapshots/client.json
+++ b/luci-app-homeproxy-pro/tests/snapshots/client.json
@@ -40,10 +40,6 @@
"ruleset",
"Rule Set"
],
- [
- "dns_cache",
- "DNS Settings"
- ],
[
"control",
"Access Control"
@@ -275,7 +271,7 @@
],
"subsection": null,
"props": {
- "validate": "fn(376:3a1eaf5f61c2)"
+ "validate": "fn(349:31d7a7fd11ff)"
}
},
{
@@ -498,7 +494,7 @@
"subsection": null,
"props": {
"default": "nil",
- "load": "fn(347:bdff551797c2)",
+ "load": "fn(347:1e6b4e82357b)",
"rmempty": false
}
},
@@ -512,7 +508,7 @@
"subsection": null,
"props": {
"default": "default-dns",
- "load": "fn(326:345de0db50cf)",
+ "load": "fn(326:b9d3cd52289f)",
"rmempty": false
}
}
@@ -597,7 +593,7 @@
"values": [],
"subsection": null,
"props": {
- "load": "fn(356:d9c0755071ec)",
+ "load": "fn(356:8d5ad606a86f)",
"modalonly": true
}
},
@@ -673,7 +669,7 @@
"subsection": null,
"props": {
"editable": true,
- "load": "fn(284:530aca6575b5)",
+ "load": "fn(284:01fc7f231969)",
"validate": "fn(487:34bee8692641)"
}
},
@@ -1028,7 +1024,7 @@
"values": [],
"subsection": null,
"props": {
- "load": "fn(219:b7b6c816012f)",
+ "load": "fn(219:0275f2977497)",
"modalonly": true
}
},
@@ -1102,7 +1098,7 @@
"subsection": null,
"props": {
"editable": true,
- "load": "fn(263:6f52e51cea77)",
+ "load": "fn(263:a19a1b2bf7ba)",
"rmempty": false
}
},
@@ -1115,6 +1111,10 @@
[
"action",
"route"
+ ],
+ [
+ "action",
+ "route-options"
]
],
"values": [],
@@ -1133,6 +1133,10 @@
[
"action",
"route"
+ ],
+ [
+ "action",
+ "route-options"
]
],
"values": [],
@@ -1151,6 +1155,10 @@
[
"action",
"route"
+ ],
+ [
+ "action",
+ "route-options"
]
],
"values": [],
@@ -1168,6 +1176,10 @@
[
"action",
"route"
+ ],
+ [
+ "action",
+ "route-options"
]
],
"values": [],
@@ -1185,6 +1197,10 @@
[
"action",
"route"
+ ],
+ [
+ "action",
+ "route-options"
]
],
"values": [],
@@ -1203,7 +1219,15 @@
[
"action",
"route"
- ]
+ ],
+ [
+ "action",
+ "route-options"
+ ],
+ {
+ "tls_fragment": "1",
+ "!reverse": true
+ }
],
"values": [],
"subsection": null,
@@ -1220,7 +1244,15 @@
[
"action",
"route"
- ]
+ ],
+ [
+ "action",
+ "route-options"
+ ],
+ {
+ "tls_record_fragment": "1",
+ "!reverse": true
+ }
],
"values": [],
"subsection": null,
@@ -1256,6 +1288,10 @@
[
"action",
"route"
+ ],
+ [
+ "action",
+ "route-options"
]
],
"values": [],
@@ -1275,6 +1311,10 @@
"action",
"route"
],
+ [
+ "action",
+ "route-options"
+ ],
[
"tls_spoof",
{}
@@ -1321,7 +1361,7 @@
"values": [],
"subsection": null,
"props": {
- "load": "fn(356:d9c0755071ec)",
+ "load": "fn(356:8d5ad606a86f)",
"modalonly": true
}
},
@@ -1540,7 +1580,8 @@
"values": [],
"subsection": null,
"props": {
- "modalonly": true
+ "modalonly": true,
+ "validate": "fn(184:0290625d9d6f)"
}
},
{
@@ -1703,7 +1744,8 @@
"values": [],
"subsection": null,
"props": {
- "modalonly": true
+ "modalonly": true,
+ "validate": "fn(184:0290625d9d6f)"
}
}
],
@@ -1712,15 +1754,99 @@
"props": {}
},
{
- "kind": "SectionValue",
- "name": "_dns",
- "description": "dns",
+ "kind": "Value",
+ "name": "dns_server",
+ "title": "Overseas DNS server",
+ "description": "Resolves every domain that is not on the China DNS path, through the selected proxy node. A domestic resolver answers these queries with polluted or NXDOMAIN results, which is what makes a foreign site fail to resolve while the proxy itself works - so only overseas services are offered here. Support UDP, TCP, DoH, DoQ, DoT. TCP protocol will be used if not specified.",
+ "depends": [
+ {
+ "routing_mode": "custom",
+ "!reverse": true
+ }
+ ],
+ "values": [
+ [
+ "1.1.1.1",
+ "CloudFlare Public DNS (1.1.1.1)"
+ ],
+ [
+ "208.67.222.222",
+ "Cisco Public DNS (208.67.222.222)"
+ ],
+ [
+ "8.8.8.8",
+ "Google Public DNS (8.8.8.8)"
+ ]
+ ],
+ "subsection": null,
+ "props": {
+ "default": "8.8.8.8",
+ "rmempty": false,
+ "validate": "fn(730:48f7f360fbd2)"
+ }
+ },
+ {
+ "kind": "Value",
+ "name": "china_dns_server",
+ "title": "China DNS server",
+ "description": "Resolves the domains on the China path, so it has to be a domestic service to get the correct local CDN answers. Keep the default; the upstream may block UDP/53, in which case a DoH address (https://dns.alidns.com/dns-query, https://doh.pub/dns-query) also works. Support UDP, TCP, DoH, DoQ, DoT. A DoH/DoT address here also sends the bootstrap lookup back to the WAN resolver: only a bare IP can resolve the proxy DNS endpoint without the ISP.",
"depends": [
[
"routing_mode",
- "custom"
+ "bypass_mainland_china"
]
],
+ "values": [
+ [
+ "wan",
+ "WAN DNS (read from interface)"
+ ],
+ [
+ "223.5.5.5",
+ "Aliyun Public DNS (223.5.5.5)"
+ ],
+ [
+ "210.2.4.8",
+ "CNNIC Public DNS (210.2.4.8)"
+ ],
+ [
+ "119.29.29.29",
+ "Tencent Public DNS (119.29.29.29)"
+ ],
+ [
+ "117.50.10.10",
+ "ThreatBook Public DNS (117.50.10.10)"
+ ]
+ ],
+ "subsection": null,
+ "props": {
+ "default": "223.5.5.5",
+ "rmempty": false,
+ "validate": "fn(624:299cd4e865be)"
+ }
+ },
+ {
+ "kind": "Flag",
+ "name": "cn_ip_fallback",
+ "title": "CN-IP DNS fallback (sing-box 1.14)",
+ "description": "When the main DNS returns a mainland China IP, re-resolve via China DNS using evaluate/match_response.",
+ "depends": [
+ [
+ "routing_mode",
+ "bypass_mainland_china"
+ ]
+ ],
+ "values": [],
+ "subsection": null,
+ "props": {
+ "rmempty": false
+ }
+ },
+ {
+ "kind": "SectionValue",
+ "name": "_dns",
+ "description": "dns",
+ "depends": [],
"values": [],
"subsection": {
"id": "GridSection:_dns#5",
@@ -1733,7 +1859,12 @@
"name": "default_strategy",
"title": "Default DNS strategy",
"description": "The DNS strategy for resolving the domain name in the address.",
- "depends": [],
+ "depends": [
+ [
+ "routing_mode",
+ "custom"
+ ]
+ ],
"values": [
[
"",
@@ -1764,12 +1895,17 @@
"name": "default_server",
"title": "Default DNS server",
"description": null,
- "depends": [],
+ "depends": [
+ [
+ "routing_mode",
+ "custom"
+ ]
+ ],
"values": [],
"subsection": null,
"props": {
"default": "default-dns",
- "load": "fn(326:345de0db50cf)",
+ "load": "fn(326:b9d3cd52289f)",
"rmempty": false
}
},
@@ -1803,7 +1939,12 @@
"name": "client_subnet",
"title": "EDNS Client subnet",
"description": "Append a edns0-subnet OPT extra record with the specified IP prefix to every query by default.
If value is an IP address instead of prefix, /32 or /128 will be appended automatically.",
- "depends": [],
+ "depends": [
+ [
+ "routing_mode",
+ "custom"
+ ]
+ ],
"values": [],
"subsection": null,
"props": {
@@ -2009,7 +2150,7 @@
"kind": "DynamicList",
"name": "headers",
"title": "Headers",
- "description": "Additional headers to be sent to the DNS server.",
+ "description": "Additional headers to be sent to the DNS server, one Name: Value per line.",
"depends": [
[
"type",
@@ -2064,7 +2205,7 @@
"values": [],
"subsection": null,
"props": {
- "load": "fn(384:9fa59da460cf)",
+ "load": "fn(384:2a0361acfbde)",
"modalonly": true,
"validate": "fn(322:e76c15d647fc)"
}
@@ -2118,7 +2259,7 @@
"props": {
"default": "direct-out",
"editable": true,
- "load": "fn(263:6f52e51cea77)",
+ "load": "fn(263:a19a1b2bf7ba)",
"rmempty": false
}
}
@@ -2242,7 +2383,8 @@
"values": [],
"subsection": null,
"props": {
- "modalonly": true
+ "modalonly": true,
+ "validate": "fn(286:47dea50f33ea)"
}
},
{
@@ -2332,7 +2474,7 @@
"values": [],
"subsection": null,
"props": {
- "load": "fn(219:b7b6c816012f)",
+ "load": "fn(219:0275f2977497)",
"modalonly": true
}
},
@@ -2410,7 +2552,7 @@
"subsection": null,
"props": {
"editable": true,
- "load": "fn(326:345de0db50cf)",
+ "load": "fn(326:b9d3cd52289f)",
"rmempty": false
}
},
@@ -2880,7 +3022,8 @@
"values": [],
"subsection": null,
"props": {
- "modalonly": true
+ "modalonly": true,
+ "validate": "fn(184:0290625d9d6f)"
}
},
{
@@ -3068,7 +3211,8 @@
"values": [],
"subsection": null,
"props": {
- "modalonly": true
+ "modalonly": true,
+ "validate": "fn(184:0290625d9d6f)"
}
}
],
@@ -3146,9 +3290,13 @@
"kind": "ListValue",
"name": "format",
"title": "Format",
- "description": null,
+ "description": "Leave empty to infer it from a .json or .srs path/url; sing-box requires it for any other extension.",
"depends": [],
"values": [
+ [
+ "",
+ "Automatic (by extension)"
+ ],
[
"binary",
"Binary file"
@@ -3160,8 +3308,7 @@
],
"subsection": null,
"props": {
- "default": "binary",
- "rmempty": false
+ "rmempty": true
}
},
{
@@ -3217,7 +3364,7 @@
"values": [],
"subsection": null,
"props": {
- "load": "fn(293:8a1c8158e8ba)"
+ "load": "fn(293:81de93c500ec)"
}
},
{
@@ -3278,177 +3425,6 @@
},
"props": {}
},
- {
- "kind": "Value",
- "name": "dns_server",
- "title": "Overseas DNS server",
- "description": "Resolves every domain that is not on the China DNS path, through the selected proxy node. A domestic resolver answers these queries with polluted or NXDOMAIN results, which is what makes a foreign site fail to resolve while the proxy itself works - so only overseas services are offered here. Support UDP, TCP, DoH, DoQ, DoT. TCP protocol will be used if not specified.",
- "depends": [
- {
- "routing_mode": "custom",
- "!reverse": true
- }
- ],
- "values": [
- [
- "1.1.1.1",
- "CloudFlare Public DNS (1.1.1.1)"
- ],
- [
- "208.67.222.222",
- "Cisco Public DNS (208.67.222.222)"
- ],
- [
- "8.8.8.8",
- "Google Public DNS (8.8.8.8)"
- ]
- ],
- "subsection": null,
- "props": {
- "default": "8.8.8.8",
- "rmempty": false,
- "validate": "fn(730:48f7f360fbd2)"
- }
- },
- {
- "kind": "Value",
- "name": "china_dns_server",
- "title": "China DNS server",
- "description": "Resolves the domains on the China path, so it has to be a domestic service to get the correct local CDN answers. Keep the default; the upstream may block UDP/53, in which case a DoH address (https://dns.alidns.com/dns-query, https://doh.pub/dns-query) also works. Support UDP, TCP, DoH, DoQ, DoT.",
- "depends": [
- [
- "routing_mode",
- "bypass_mainland_china"
- ]
- ],
- "values": [
- [
- "wan",
- "WAN DNS (read from interface)"
- ],
- [
- "223.5.5.5",
- "Aliyun Public DNS (223.5.5.5)"
- ],
- [
- "210.2.4.8",
- "CNNIC Public DNS (210.2.4.8)"
- ],
- [
- "119.29.29.29",
- "Tencent Public DNS (119.29.29.29)"
- ],
- [
- "117.50.10.10",
- "ThreatBook Public DNS (117.50.10.10)"
- ]
- ],
- "subsection": null,
- "props": {
- "default": "223.5.5.5",
- "rmempty": false,
- "validate": "fn(624:299cd4e865be)"
- }
- },
- {
- "kind": "Flag",
- "name": "cn_ip_fallback",
- "title": "CN-IP DNS fallback (sing-box 1.14)",
- "description": "When the main DNS returns a mainland China IP, re-resolve via China DNS using evaluate/match_response.",
- "depends": [
- [
- "routing_mode",
- "bypass_mainland_china"
- ]
- ],
- "values": [],
- "subsection": null,
- "props": {
- "rmempty": false
- }
- },
- {
- "kind": "SectionValue",
- "name": "_dns_cache",
- "description": "dns",
- "depends": [
- {
- "routing_mode": "custom",
- "!reverse": true
- }
- ],
- "values": [],
- "subsection": {
- "id": "GridSection:_dns_cache#9",
- "kind": "GridSection",
- "name": "_dns_cache",
- "tabs": [],
- "options": [
- {
- "kind": "Flag",
- "name": "optimistic_cache",
- "title": "Optimistic DNS cache",
- "description": "Return expired cache immediately and refresh in background (sing-box 1.14).",
- "depends": [
- [
- "disable_cache",
- "0"
- ],
- [
- "disable_cache_expire",
- "0"
- ]
- ],
- "values": [],
- "subsection": null,
- "props": {
- "rmempty": false
- }
- },
- {
- "kind": "Value",
- "name": "optimistic_timeout",
- "title": "Optimistic cache timeout",
- "description": "Max time an expired entry may be served. Examples: 3d, 1h.",
- "depends": [],
- "values": [],
- "subsection": null,
- "props": {}
- },
- {
- "kind": "Value",
- "name": "dns_timeout",
- "title": "DNS query timeout",
- "description": "Default timeout per DNS query in seconds (sing-box default: 10).",
- "depends": [],
- "values": [],
- "subsection": null,
- "props": {
- "datatype": "uinteger"
- }
- },
- {
- "kind": "Flag",
- "name": "cache_file_store_dns",
- "title": "Store DNS cache",
- "description": "Persist DNS cache across restarts (sing-box 1.14, replaces Store RDRC).",
- "depends": [
- [
- "disable_cache",
- "0"
- ]
- ],
- "values": [],
- "subsection": null,
- "props": {
- "rmempty": false
- }
- }
- ],
- "subsection": null
- },
- "props": {}
- },
{
"kind": "SectionValue",
"name": "_control",
@@ -3456,7 +3432,7 @@
"depends": [],
"values": [],
"subsection": {
- "id": "GridSection:_control#10",
+ "id": "GridSection:_control#9",
"kind": "GridSection",
"name": "_control",
"tabs": [
@@ -3512,12 +3488,12 @@
"kind": "ListValue",
"name": "lan_proxy_mode",
"title": "Proxy filter mode",
- "description": null,
+ "description": "\"No filter\" proxies every LAN client, which is the default; the other two values select clients by the lists below.",
"depends": [],
"values": [
[
"disabled",
- "Disable"
+ "No filter (proxy every client)"
],
[
"listed_only",
diff --git a/luci-app-homeproxy-pro/tests/ucode/test_firewall_template.sh b/luci-app-homeproxy-pro/tests/ucode/test_firewall_template.sh
index e6568e9c3..60e0dd994 100644
--- a/luci-app-homeproxy-pro/tests/ucode/test_firewall_template.sh
+++ b/luci-app-homeproxy-pro/tests/ucode/test_firewall_template.sh
@@ -96,6 +96,28 @@ if ! grep -q '\.dnsmasq-nftset' "$ROOT/root/etc/init.d/homeproxy-pro"; then
FAILED=1
fi
+# --- ipv6_support off refuses IPv6 forwarding ------------------------------
+# The DNS side answers AAAA with REFUSED, but only for clients that ask this
+# router: a cached address or a client with its own resolver still reaches the
+# WAN. The guard is therefore a forward-hook base chain of its own - fw4's
+# forward chain jumps into the zone chains, whose accept rules end the traversal
+# before anything appended at the end of it is reached - ordered ahead of fw4's
+# forward (priority mangle vs filter).
+#
+# Asserted at the source level, like the gfwlist gate below: the render uses
+# the device's own UCI, so the ipv6_support branch cannot be exercised in both
+# of its states from here.
+GUARD_COND="$(grep -c "if (ipv6_support !== '1'):" "$TEMPLATE_SRC")"
+GUARD_HOOK="$(grep -c 'type filter hook forward priority mangle' "$TEMPLATE_SRC")"
+GUARD_RULE="$(grep -c 'meta nfproto ipv6 counter reject' "$TEMPLATE_SRC")"
+
+if [ "$GUARD_COND" -ne 1 ] || [ "$GUARD_HOOK" -ne 1 ] || [ "$GUARD_RULE" -ne 1 ]; then
+ echo "FAIL: the IPv6 forwarding guard is incomplete or ungated"
+ echo " condition=$GUARD_COND hook=$GUARD_HOOK rule=$GUARD_RULE (each must be 1)"
+ echo " a guard on priority filter would run after fw4's forward chain and never match"
+ FAILED=1
+fi
+
# --- layer 2: the render -------------------------------------------------
# The render output and its stderr live beside the staged template, never at a
# fixed /tmp path. A caller that passes a work dir (tests/ucode/run.sh hands
@@ -181,6 +203,14 @@ if grep -nE "^#[^!].*homeproxy_[a-z0-9_]+ \{" "$OUT" > "/dev/null"; then
FAILED=1
fi
+# The template defaults ipv6_support to '0', so the guard chain has to appear in
+# the render both on a device without the switch set and in CI, where there is
+# no /etc/config/homeproxy-pro at all.
+if ! grep -q "^chain homeproxy_ipv6_guard {" "$OUT"; then
+ echo "FAIL: the IPv6 forwarding guard chain is missing from the rendered template"
+ FAILED=1
+fi
+
if [ "$FAILED" -eq 0 ]; then
echo "PASS: firewall_post.ut renders homeproxy-pro objects as standalone nft statements"
echo "PASS: the gfwlist gate follows the dnsmasq nftset capability"
diff --git a/luci-app-homeproxy-pro/tests/ucode/test_generators.sh b/luci-app-homeproxy-pro/tests/ucode/test_generators.sh
index 87cf6b6a9..0e61320ba 100755
--- a/luci-app-homeproxy-pro/tests/ucode/test_generators.sh
+++ b/luci-app-homeproxy-pro/tests/ucode/test_generators.sh
@@ -975,7 +975,7 @@ else
esac
fi
-# 3) P2 #4 / §2.2.1: the three NAPTR (qtype 35) bypass suffixes must be
+# 3) P2 #4 / §2.2.1: the NAPTR (qtype 35) bypass suffixes must be
# emitted as a single domain_suffix entry backed by the
# NAPTR_BYPASS_SUFFIXES constant. ucode's pretty-printer emits the
# query_type array and the domain_suffix array as multi-line:
@@ -983,7 +983,6 @@ fi
# 35
# ],
# "domain_suffix": [
-# "r.10086.cn",
# "10086.cn",
# "pub.3gppnetwork.org"
# ],
@@ -1000,7 +999,7 @@ else
# naptr_body should contain the three suffixes in order. If
# any is missing, the layout shifted and the test fails loud.
naptr_ok=1
- for sfx in 'r.10086.cn' '10086.cn' 'pub.3gppnetwork.org'; do
+ for sfx in '10086.cn' 'pub.3gppnetwork.org'; do
if ! printf '%s\n' "$naptr_body" | grep -q "\"$sfx\""; then
echo "FAIL: naptr-bypass-domains: NAPTR rule is missing suffix \"$sfx\""
echo " grep window:"
@@ -1009,7 +1008,7 @@ else
fi
done
if [ "$naptr_ok" = 1 ]; then
- echo "PASS: naptr-bypass-domains: qtype-35 rule domain_suffix carries the 3 NAPTR_BYPASS_SUFFIXES entries"
+ echo "PASS: naptr-bypass-domains: qtype-35 rule domain_suffix carries every NAPTR_BYPASS_SUFFIXES entry"
fi
fi
@@ -1084,4 +1083,220 @@ run_case_type_error local-ruleset-bad-path "outside the homeproxy-pro whitelist"
# covered by manual testing on r28. Re-add this case via a
# custom_extra_tags.uci fixture when convenient.
-exit ${FAILED:-0}
\ No newline at end of file
+
+# --- review 2026-09-30: fields sing-box refuses at load time ----------------
+#
+# Each of these reached sing-box as a configuration it rejects outright - an
+# unknown field, an empty action object, a mutually exclusive pair - and the
+# only symptom the user saw was "my setting did not take". The generator now
+# refuses them by name before writing anything, which is what
+# run_case_type_error observes.
+cat > "$WORK/dns-field-guard.uc" <<'EOF'
+'use strict';
+
+import { readfile } from 'fs';
+
+const config = json(readfile(ARGV[0]));
+const mode = ARGV[1];
+
+function has_aaaa() {
+ for (let r in (config.dns?.rules || []))
+ if (r.action === 'reject' && type(r.query_type) === 'array' && r.query_type[0] === 28)
+ return true;
+ return false;
+}
+
+switch (mode) {
+case 'headers':
+ for (let s in (config.dns?.servers || [])) {
+ if (s.tag !== 'cfg-ds_https-dns')
+ continue;
+ if (type(s.headers) !== 'object') {
+ print('headers is not an object');
+ exit(1);
+ }
+ if (length(s.headers['X-Test'] || []) !== 2) {
+ print('X-Test is not a two-value list');
+ exit(1);
+ }
+ if (s.headers['X-Test'][0] !== 'one' || s.headers['X-Test'][1] !== 'two') {
+ print('X-Test values are wrong');
+ exit(1);
+ }
+ if ((s.headers['Authorization'] || [])[0] !== 'Bearer t') {
+ print('Authorization is wrong');
+ exit(1);
+ }
+ print('OK');
+ exit(0);
+ }
+ print('cfg-ds_https-dns is missing');
+ exit(1);
+case 'aaaa-present':
+ print(has_aaaa() ? 'OK' : 'the AAAA reject rule is missing');
+ exit(has_aaaa() ? 0 : 1);
+case 'aaaa-absent':
+ print(has_aaaa() ? 'an AAAA reject rule was emitted with ipv6_support on' : 'OK');
+ exit(has_aaaa() ? 1 : 0);
+case 'evaluate-timeout':
+ for (let r in (config.dns?.rules || []))
+ if (r.action === 'evaluate') {
+ print(r.timeout === '5s' ? 'OK' : 'the evaluate rule carries no 5s timeout: ' + r.timeout);
+ exit(r.timeout === '5s' ? 0 : 1);
+ }
+ print('no evaluate DNS rule was generated');
+ exit(1);
+case 'http-client-detour':
+ for (let c in (config.http_clients || [])) {
+ if (c.detour !== 'main-out') {
+ print('an http_client detours through ' + c.detour);
+ exit(1);
+ }
+ }
+ print('OK');
+ exit(0);
+case 'update-interval':
+ for (let rs in (config.route?.rule_set || []))
+ if (rs.tag === 'cfg-rs_remote-rule') {
+ print(rs.update_interval === '3600s' ? 'OK' : 'update_interval is ' + rs.update_interval);
+ exit(rs.update_interval === '3600s' ? 0 : 1);
+ }
+ print('cfg-rs_remote-rule is missing');
+ exit(1);
+}
+print('unknown mode');
+exit(1);
+EOF
+
+assert_guard() {
+ local name="$1" json="$2" mode="$3" want="$4"
+ local got
+
+ if [ ! -s "$json" ]; then
+ echo "FAIL: $name: no configuration was generated"
+ FAILED=1
+ return
+ fi
+
+ got="$(ucode "$WORK/dns-field-guard.uc" "$json" "$mode" 2>&1)"
+ if [ "$got" = "$want" ]; then
+ echo "PASS: $name"
+ else
+ echo "FAIL: $name: $got"
+ FAILED=1
+ fi
+}
+
+# 1) DNS server headers: the UCI string list has to become
+# map[string][]string, not a JSON array.
+run_case dns-headers-object "$ROOT/tests/fixtures/generators/dns_headers.uci" \
+ generate_client.uc sing-box-c.json
+assert_guard "dns-headers-object: the UCI list became a header object" \
+ "$WORK/dns-headers-object/run/sing-box-c.json" headers OK
+
+# 2) The baseline both variations below are measured against: with the flags
+# off, the fixture has to generate and pass sing-box check. Without it a
+# refusal in 3)-5) could just as well come from the fixture itself.
+run_case dns-action-guards-baseline "$ROOT/tests/fixtures/generators/dns_action_guards.uci" \
+ generate_client.uc sing-box-c.json
+
+# 3) race + speculative on one rule.
+run_case_type_error dns-race-speculative "enables both race and speculative" \
+ "$ROOT/tests/fixtures/generators/dns_action_guards.uci" generate_client.uc sing-box-c.json \
+ "s#option race '0'#option race '1'#; s#option speculative '0'#option speculative '1'#"
+
+# 4) race on an action that is not final (evaluate).
+run_case_type_error dns-race-final-action "race is only allowed on a final action" \
+ "$ROOT/tests/fixtures/generators/dns_action_guards.uci" generate_client.uc sing-box-c.json \
+ "s#option action 'route'#option action 'evaluate'#; s#option race '0'#option race '1'#"
+
+# 5) client_subnet + remove_client_subnet.
+run_case_type_error dns-client-subnet-conflict "sets both client_subnet and remove_client_subnet" \
+ "$ROOT/tests/fixtures/generators/dns_action_guards.uci" generate_client.uc sing-box-c.json \
+ "s#option remove_client_subnet '0'#option remove_client_subnet '1'#"
+
+# 6) route-options with no payload at all.
+run_case_type_error routing-route-options-empty "selects the route-options action but sets none of its fields" \
+ "$ROOT/tests/fixtures/generators/routing_rule_route_options_empty.uci" generate_client.uc sing-box-c.json
+
+# 7) tls_fragment + tls_record_fragment.
+run_case_type_error routing-tls-fragment-conflict "enables both tls_fragment and tls_record_fragment" \
+ "$ROOT/tests/fixtures/generators/routing_rule_tls_fragment.uci" generate_client.uc sing-box-c.json
+
+# 8) AAAA is refused when the router does not proxy IPv6, and left alone when
+# it does. firewall_post.ut emits no IPv6 rule at all with the switch off,
+# so a AAAA answer was the client's ticket straight out over the WAN.
+run_case ipv6-guard-off "$ROOT/tests/fixtures/generators/custom.uci" \
+ generate_client.uc sing-box-c.json
+assert_guard "ipv6-guard-off: AAAA is refused while ipv6_support is off" \
+ "$WORK/ipv6-guard-off/run/sing-box-c.json" aaaa-present OK
+
+run_case ipv6-guard-on "$ROOT/tests/fixtures/generators/client.uci" \
+ generate_client.uc sing-box-c.json \
+ "s#option ipv6_support '0'#option ipv6_support '1'#"
+assert_guard "ipv6-guard-on: no AAAA rule while ipv6_support is on" \
+ "$WORK/ipv6-guard-on/run/sing-box-c.json" aaaa-absent OK
+
+# 9) evaluate carries the same timeout override as route: emitting it for
+# route only made the field silently ineffective on an evaluate rule.
+run_case dns-evaluate-timeout "$ROOT/tests/fixtures/generators/dns_action_guards.uci" \
+ generate_client.uc sing-box-c.json
+assert_guard "dns-evaluate-timeout: the evaluate rule kept its query timeout" \
+ "$WORK/dns-evaluate-timeout/run/sing-box-c.json" evaluate-timeout OK
+
+# 10) A bare-number update_interval is a duration to sing-box; without a unit
+# it reports "time: missing unit in duration" and refuses everything.
+run_case ruleset-update-interval "$ROOT/tests/fixtures/generators/ruleset_remote_update_interval.uci" \
+ generate_client.uc sing-box-c.json
+assert_guard "ruleset-update-interval: '3600' became '3600s'" \
+ "$WORK/ruleset-update-interval/run/sing-box-c.json" update-interval OK
+
+# 11) A rule pointing at a routing_node that was switched off used to emit a
+# tag nothing generates ("outbound not found: cfg-rn_off-out").
+run_case_type_error routing-disabled-node "is disabled" \
+ "$ROOT/tests/fixtures/generators/routing_rule_disabled_node.uci" generate_client.uc sing-box-c.json
+
+# 12) initial_path outside the whitelist: the same gate as a local rule-set
+# path, and the same fail-loud treatment (it used to be dropped silently).
+run_case_type_error ruleset-bad-initial-path "initial_path" \
+ "$ROOT/tests/fixtures/generators/ruleset_remote_bad_initial_path.uci" generate_client.uc sing-box-c.json
+
+# 13) A preset mode downloads its remote rule-sets through the main outbound,
+# not direct-out: a router that can only reach the rule-set host through
+# the proxy otherwise just logs a failed download.
+run_case ruleset-remote-preset "$ROOT/tests/fixtures/generators/ruleset_remote_preset.uci" \
+ generate_client.uc sing-box-c.json
+assert_guard "ruleset-remote-preset: the remote rule-set downloads through main-out" \
+ "$WORK/ruleset-remote-preset/run/sing-box-c.json" http-client-detour OK
+
+# 14) A missing china_ip4.json must not leave a `type: local` rule-set - and
+# the rule matching it - pointing at a path sing-box cannot open: that
+# fails the whole configuration, not just the rule. The mode keeps
+# working through geoip-cn. The staged bypass-mode case is reused with
+# the file removed and put back.
+CHINA_CASE="$WORK/client"
+if [ -f "$CHINA_CASE/resources/china_ip4.json" ]; then
+ mv "$CHINA_CASE/resources/china_ip4.json" "$CHINA_CASE/resources/china_ip4.json.bak"
+ rm -f "$CHINA_CASE/run/sing-box-c.json"
+ if ( cd "$CHINA_CASE/scripts" && ucode -L "$CHINA_CASE/scripts" generate_client.uc 2>"$CHINA_CASE/missing.err" ); then
+ if grep -q '"china-ip"' "$CHINA_CASE/run/sing-box-c.json"; then
+ echo "FAIL: china-ip-missing: the configuration still references the missing rule-set"
+ FAILED=1
+ elif grep -q '"tag": "geoip-cn"' "$CHINA_CASE/run/sing-box-c.json"; then
+ echo "PASS: china-ip-missing: the mainland split falls back to geoip-cn"
+ else
+ echo "FAIL: china-ip-missing: neither china-ip nor geoip-cn is declared"
+ FAILED=1
+ fi
+ else
+ echo "FAIL: china-ip-missing: generation failed"
+ head -3 "$CHINA_CASE/missing.err"
+ FAILED=1
+ fi
+ mv "$CHINA_CASE/resources/china_ip4.json.bak" "$CHINA_CASE/resources/china_ip4.json"
+else
+ echo "FAIL: china-ip-missing: the client case did not stage china_ip4.json"
+ FAILED=1
+fi
+
+exit ${FAILED:-0}
diff --git a/luci-app-homeproxy-pro/tests/ucode/test_rpc_methods.sh b/luci-app-homeproxy-pro/tests/ucode/test_rpc_methods.sh
index 92dddc085..737b7ada1 100755
--- a/luci-app-homeproxy-pro/tests/ucode/test_rpc_methods.sh
+++ b/luci-app-homeproxy-pro/tests/ucode/test_rpc_methods.sh
@@ -224,9 +224,9 @@ check('certificate_write tolerates an empty request', threw == null, threw);
* executed. Four of these were referenced by no test at all. */
const all_methods = [
'acllist_read', 'acllist_write', 'certificate_write', 'connection_check',
- 'log_clean', 'node_parse', 'resources_get_version', 'resources_update',
- 'singbox_generator', 'singbox_get_features', 'update_subscriptions',
- 'update_subscriptions_status'
+ 'gfw_set_counts', 'log_clean', 'node_parse', 'resources_get_version',
+ 'resources_update', 'singbox_generator', 'singbox_get_features',
+ 'update_subscriptions', 'update_subscriptions_status'
];
for (let m in all_methods) {
diff --git a/luci-app-passwall2/Makefile b/luci-app-passwall2/Makefile
index 269c477a6..e10dd3413 100644
--- a/luci-app-passwall2/Makefile
+++ b/luci-app-passwall2/Makefile
@@ -6,8 +6,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall2
-PKG_VERSION:=26.9.16
-PKG_RELEASE:=131
+PKG_VERSION:=26.10.1
+PKG_RELEASE:=132
PKG_PO_VERSION:=$(PKG_VERSION)
PKG_CONFIG_DEPENDS:= \
diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua
index 2bc93b48b..e6fc4c9bc 100644
--- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua
+++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/node_subscribe_config.lua
@@ -271,11 +271,12 @@ o:value("proxy", translate("Proxy"))
o = s:option(Value, "user_agent", translate("User-Agent"))
o.default = "v2rayN/9.99"
-o:value("curl", "Curl")
-o:value("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0", "Edge for Linux")
-o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0", "Edge for Windows")
o:value("passwall2", "PassWall2")
o:value("v2rayN/9.99", "v2rayN")
+o:value("clash.meta/v9.99.99", "Clash.Meta")
+o:value("Shadowrocket/9999 CFNetwork/3896.100.1.2.1 Darwin/27.0.0 iPhone19,7", "Shadowrocket")
+o:value("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0", "Edge for Linux")
+o:value("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0", "Edge for Windows")
o = s:option(ListValue, "chain_proxy", translate("Chain Proxy"))
o:value("", translate("Close(Not use)"))
diff --git a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/1_sing-box.lua b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/1_sing-box.lua
index 444b52b05..de4d6fd7f 100644
--- a/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/1_sing-box.lua
+++ b/luci-app-passwall2/luasrc/model/cbi/passwall2/client/type/1_sing-box.lua
@@ -560,10 +560,18 @@ o:depends({ protocol = "naive" })
o = s:option(Flag, "tls_allowInsecure", translate("allowInsecure"), translate("Whether unsafe connections are allowed. When checked, Certificate validation will be skipped."))
o.default = "0"
o:depends({ tls = true })
-o:depends({ protocol = "hysteria"})
+o:depends({ protocol = "hysteria" })
o:depends({ protocol = "tuic" })
o:depends({ protocol = "hysteria2" })
+o = s:option(Value, "tls_pinSHA256", translate("TLS Chain Fingerprint (SHA256)"))
+o:depends({ tls = true })
+o:depends({ protocol = "hysteria" })
+o:depends({ protocol = "tuic" })
+o:depends({ protocol = "hysteria2" })
+o.description = translate("Once set, connects only when the server’s chain fingerprint matches.") ..
+ string.format("%s", "→ " .. translate("Fetch Manually"))
+
o = s:option(Flag, "tls_certificate", translate("TLS Certificate (PEM)"))
o.default = "0"
o:depends({ tls = true, reality = false })
diff --git a/luci-app-passwall2/luasrc/passwall2/api.lua b/luci-app-passwall2/luasrc/passwall2/api.lua
index c0ef7b770..b67daed9c 100644
--- a/luci-app-passwall2/luasrc/passwall2/api.lua
+++ b/luci-app-passwall2/luasrc/passwall2/api.lua
@@ -227,16 +227,19 @@ function exec_call(cmd)
end
function base64Decode(text)
- if not text then return '' end
- local encoded = text:gsub("%z", ""):gsub("%c", ""):gsub("_", "/"):gsub("-", "+")
+ if type(text) ~= "string" then return "" end
+ local encoded = text:gsub("%z", ""):gsub("%c", ""):gsub("_", "/"):gsub("-", "+"):gsub("=+$", "")
+ if encoded == "" then return text end
+ if not encoded:match("^[A-Za-z0-9+/]*$") then return text end
local mod4 = #encoded % 4
- encoded = encoded .. string.sub('====', mod4 + 1)
- local result = nixio.bin.b64decode(encoded)
- if result then
- return result:gsub("%z", "")
- else
- return text
- end
+ if mod4 == 1 then return text end
+ local padded = encoded .. string.rep("=", (4 - mod4) % 4)
+ local result = nixio.bin.b64decode(padded)
+ if not result then return text end
+ -- Verify that the normalized input is canonical Base64.
+ local reencoded = nixio.bin.b64encode(result):gsub("=+$", "")
+ if reencoded ~= encoded then return text end
+ return (result:gsub("%z", ""))
end
function base64Encode(text)
@@ -1866,6 +1869,25 @@ function fetch_cert_sha256(host, port, sni, timeout)
return fp:upper()
end
+function sha256_xray_sb(str)
+ local decoded = base64Decode(str)
+ if decoded ~= str and #decoded == 32 then return str end
+ local hex = str:gsub(":", "")
+ if #hex ~= 64 or not hex:match("^[A-Fa-f0-9]+$") then return str end
+ local binary = hex:gsub("%x%x", function(byte)
+ return string.char(tonumber(byte, 16))
+ end)
+ return base64Encode(binary)
+end
+
+function sha256_sb_xray(str)
+ local binary = base64Decode(str)
+ if binary == str or #binary ~= 32 then return str end
+ return (binary:gsub(".", function(byte)
+ return string.format("%02X", string.byte(byte))
+ end))
+end
+
function get_dnsmasq_server_domain()
local dnsmasq_server = uci:get("dhcp", "@dnsmasq[0]", "server")
local dnsmasq_server_t = {}
diff --git a/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua b/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua
index 1f9c8197f..f47369dcf 100644
--- a/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua
+++ b/luci-app-passwall2/luasrc/passwall2/util_sing-box.lua
@@ -9,6 +9,7 @@ local ech_domain = {}
local local_version = api.get_app_version("sing-box"):match("[^v]+")
local version_ge_1_14_0 = api.compare_versions(local_version, ">=", "1.14.0")
+local version_ge_1_15_0 = api.compare_versions(local_version, ">=", "1.15.0")
local GLOBAL = {
DNS_SERVER = {}
@@ -248,7 +249,7 @@ function gen_outbound(flag, node, tag, proxy_table)
enabled = true,
disable_sni = (node.tls_disable_sni == "1") and true or false, -- Do not send the server name in ClientHello.
server_name = node.tls_serverName, -- Used to verify the hostname on the returned certificate, unless the settings are insecure. It is also included in ClientHello to support virtual hosts, unless it is an IP address.
- insecure = node.tls_allowInsecure == "1" or (node.tls_pinSHA256 and node.tls_pinSHA256 ~= ""), -- Accepts any server certificate. (Also compatible with xray's pinnedPeerCertSha256)
+ insecure = node.tls_allowInsecure == "1", -- Accepts any server certificate.
alpn = alpn, -- A list of supported application layer protocols, arranged in order of priority. If both peers support ALPN, the protocol selected will be one of these protocols; otherwise, the connection will fail.
--max_version = "1.3",
fragment = fragment,
@@ -294,6 +295,13 @@ function gen_outbound(flag, node, tag, proxy_table)
short_id = node.reality_shortId
} or nil
}
+ if version_ge_1_15_0 then
+ if node.tls_pinSHA256 and node.tls_pinSHA256 ~= "" then
+ tls.certificate_sha256 = { api.sha256_xray_sb(node.tls_pinSHA256) }
+ end
+ else
+ tls.insecure = node.tls_allowInsecure == "1" or (node.tls_pinSHA256 and node.tls_pinSHA256 ~= "")
+ end
end
local mux = nil
@@ -1488,6 +1496,10 @@ function gen_config(var)
end
end
if to_node then
+ local chained_tag = outbound.tag .. " -> " .. to_node[".name"] .. (to_node.remarks and ":" .. to_node.remarks or "")
+ for _, o in ipairs(outbounds_table) do
+ if o.tag == chained_tag then return chained_tag, last_insert_outbound end
+ end
local to_outbound
if to_node.type ~= "sing-box" then
local tag = to_node[".name"]
@@ -1549,7 +1561,7 @@ function gen_config(var)
local outbound, exist
if node.protocol == "_urltest" then
outbound, exist = gen_urltest_outbound(node)
- if exist then
+ if exist and not node.chain_proxy then
return outbound.tag
end
elseif node.protocol == "_iface" then
@@ -1583,7 +1595,7 @@ function gen_config(var)
end
if outbound then
local default_outbound_tag, last_insert_outbound = set_outbound_detour(node, outbound, outbounds)
- table.insert(outbounds, outbound)
+ if not exist then table.insert(outbounds, outbound) end
if last_insert_outbound then
table.insert(outbounds, last_insert_outbound)
end
@@ -2030,7 +2042,7 @@ function gen_config(var)
tag = fakedns_tag,
type = "fakeip",
inet4_range = "198.18.0.0/16",
- inet6_range = "fc00::/18",
+ inet6_range = "2001:2::/48",
})
if not experimental then
diff --git a/luci-app-passwall2/luasrc/passwall2/util_xray.lua b/luci-app-passwall2/luasrc/passwall2/util_xray.lua
index 2c8fe20d0..38b816b8b 100644
--- a/luci-app-passwall2/luasrc/passwall2/util_xray.lua
+++ b/luci-app-passwall2/luasrc/passwall2/util_xray.lua
@@ -186,7 +186,7 @@ function gen_outbound(flag, node, tag, proxy_table)
tlsSettings = (node.stream_security == "tls") and {
serverName = node.tls_serverName,
fingerprint = (node.type == "Xray" and node.utls == "1" and node.fingerprint and node.fingerprint ~= "") and node.fingerprint or nil,
- pinnedPeerCertSha256 = node.tls_pinSHA256 or "",
+ pinnedPeerCertSha256 = (node.tls_pinSHA256 and node.tls_pinSHA256 ~= "") and api.sha256_sb_xray(node.tls_pinSHA256) or "",
verifyPeerCertByName = node.tls_CertByName or "",
echConfigList = (node.ech == "1") and node.ech_config or nil,
certificates = (node.tls_certificate == "1" and node.tls_certificate_pem ~= "") and {
@@ -1058,6 +1058,9 @@ function gen_config(var)
function gen_loopback(outbound_tag, loopback_dst)
if not outbound_tag or outbound_tag == "" then return nil end
local inbound_tag = loopback_dst and "lo-to-" .. loopback_dst or outbound_tag .. "-lo"
+ for _, o in ipairs(outbounds) do
+ if o.tag == outbound_tag and o.protocol == "loopback" and o.settings.inboundTag == inbound_tag then return o end
+ end
local loopback_outbound = {
protocol = "loopback",
tag = outbound_tag,
@@ -1697,7 +1700,7 @@ function gen_config(var)
poolSize = 65535
}
local fakedns6 = {
- ipPool = "fc00::/18",
+ ipPool = "2001:2::/48",
poolSize = 65535
}
if remote_dns_query_strategy == "UseIP" then
diff --git a/luci-app-passwall2/luasrc/view/passwall2/node_config/header.htm b/luci-app-passwall2/luasrc/view/passwall2/node_config/header.htm
index 2840ea595..33a5da3da 100644
--- a/luci-app-passwall2/luasrc/view/passwall2/node_config/header.htm
+++ b/luci-app-passwall2/luasrc/view/passwall2/node_config/header.htm
@@ -22,7 +22,7 @@ local sid = self.section
alert(res && res.msg ? res.msg : "<%:Fetch Failed%> !");
return;
}
- const inputs = document.querySelectorAll('input[id$=".xray_tls_pinSHA256"]');
+ const inputs = document.querySelectorAll('input[id$="_tls_pinSHA256"]');
inputs.forEach(function(input) {
input.value = res.data;
});
diff --git a/luci-app-passwall2/luasrc/view/passwall2/node_config/link_share_man.htm b/luci-app-passwall2/luasrc/view/passwall2/node_config/link_share_man.htm
index 911bdce81..56160ecfd 100644
--- a/luci-app-passwall2/luasrc/view/passwall2/node_config/link_share_man.htm
+++ b/luci-app-passwall2/luasrc/view/passwall2/node_config/link_share_man.htm
@@ -41,19 +41,25 @@ local current_node = map:get(section)
}
function b64DecodeUnicode(str) {
- return decodeURIComponent(Array.prototype.map.call(atob(str), function (c) {
- return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
- }).join(''));
+ try {
+ return decodeURIComponent(Array.prototype.map.call(atob(str), function (c) {
+ return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
+ }).join(''));
+ } catch (e) {}
+ return str;
}
function b64decutf8safe(str) {
- var l;
+ const orig = str;
+ let l;
str = str.replace(/-/g, "+").replace(/_/g, "/");
l = str.length;
l = (4 - l % 4) % 4;
if (l)
str = padright(str, l, "=");
- return b64DecodeUnicode(str);
+ const text = b64DecodeUnicode(str);
+ if (text !== str) return text;
+ return orig;
}
function b64encsafe(str) {
@@ -631,6 +637,7 @@ local current_node = map:get(section)
params += opt.query("congestion_control", dom_prefix + "tuic_congestion_control");
params += opt.query("udp_relay_mode", dom_prefix + "tuic_udp_relay_mode");
params += opt.query("allowinsecure", dom_prefix + "tls_allowInsecure");
+ params += opt.query("pcs", dom_prefix + "tls_pinSHA256");
params += "#" + encodeURI(v_alias.value);
if (params[0] == "&") {
@@ -661,6 +668,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("insecure", dom_prefix + "tls_allowInsecure");
+ params += opt.query("pcs", dom_prefix + "tls_pinSHA256");
}
params += "#" + encodeURI(v_alias.value);
@@ -1001,6 +1009,7 @@ local current_node = map:get(section)
opt.set(dom_prefix + 'method', method || "");
opt.set(dom_prefix + 'ss_method', method || "");
opt.set(dom_prefix + 'tcp_fast_open', queryParam.tfo);
+
if (plugin && plugin != "none") {
plugin = (plugin === "simple-obfs") ? "obfs-local" : plugin;
opt.set(dom_prefix + 'plugin_enabled', true);
@@ -1023,9 +1032,29 @@ local current_node = map:get(section)
opt.set(dom_prefix + 'tls_allowInsecure', true);
}
}
+ // Some ShadowTLS subscriptions utilize SIP003.
+ if (plugin === "shadow-tls") {
+ pluginOpts = pluginOpts.replace(/(^|;)password=/, '$1passwd=');
+ pluginOpts = pluginOpts.replace(/(^|;)version=([123])(?=;|$)/, '$1v$2=1');
+ if (dom_prefix === "ssrust_") {
+ opt.set(dom_prefix + 'plugin_opts', pluginOpts || "");
+ } else if (dom_prefix === "singbox_") {
+ const opts = Object.fromEntries(pluginOpts.split(';').map(item => item.split('=', 2)));
+ const host = opts.host;
+ const password = opts.passwd;
+ const versionKey = Object.keys(opts).find(key => /^v[123]$/.test(key));
+ const version = versionKey ? versionKey.slice(1) : "1";
+ opt.set(dom_prefix + 'shadowtls', true);
+ opt.set(dom_prefix + 'shadowtls_version', version);
+ opt.set(dom_prefix + 'shadowtls_password', password || "");
+ opt.set(dom_prefix + 'shadowtls_serverName', host || "");
+ opt.set(dom_prefix + 'plugin_enabled', false);
+ }
+ }
} else {
opt.set(dom_prefix + 'plugin', "none");
}
+
if (param !== undefined) {
opt.set('remarks', decodeURIComponent(param));
}
@@ -1324,29 +1353,74 @@ local current_node = map:get(section)
}
}
if (link_type === "vmess") {
+ var sstr = b64DecodeUnicode((link_split[1] || "").replace(/#.*/, "").trim());
if (vmess_type == "sing-box" && has_singbox) {
dom_prefix = "singbox_";
opt.set('type', "sing-box");
- } else {
+ } else if (has_xray) {
dom_prefix = "xray_";
opt.set('type', "Xray");
}
opt.set(dom_prefix + 'protocol', "vmess");
- var sstr = b64DecodeUnicode((link_split[1] || "").replace(/#.*/, "").trim());
- var ploc = sstr.indexOf("/?");
- var url0, param = "";
- if (ploc > 0) {
- url0 = sstr.substr(0, ploc);
- param = sstr.substr(ploc + 2);
+
+ let ssm = {};
+ if (is_json(sstr)) {
+ // vmess://base64(json)
+ ssm = JSON.parse(sstr);
+ } else if (sstr.indexOf("?") > 0) {
+ // vmess://base64(auto:uuid@host:port)?tfo=1&remark=xxx&&alterId=0&obfs=websocket&path=%2F&obfsParam=host (obfs ~= ws obfsParam={})
+ sstr = sstr.replace("/?", "?");
+ const ploc = sstr.indexOf("?");
+ let url0 = sstr.substr(0, ploc);
+ let paramStr = sstr.substr(ploc + 2);
+
+ const m = new URL("http://" + b64decsafe(url0));
+ if (m.username !== "") {
+ ssm.security = m.username;
+ ssm.id = m.password || m.username;
+ } else {
+ ssm.security = "auto";
+ ssm.id = m.password || m.username;
+ }
+ ssm.add = unbracketIP(m.hostname);
+ ssm.port = m.port || "443";
+
+ const params = {};
+ const pairs = paramStr.split("&");
+ for (let i = 0; i < pairs.length; i++) {
+ const v = pairs[i];
+ const s = v.indexOf("=");
+ if (s > 0) {
+ params[v.substring(0, s)] = decodeURIComponent(v.substring(s + 1));
+ }
+ }
+ ssm.ps = params.remark || params.remarks;
+ ssm.net = (params.obfs === "websocket") ? "ws" : (params.obfs || "tcp");
+ ssm.path = params.path;
+ ssm.aid = params.alterId || "0";
+ ssm.tls = params.tls;
+ ssm.sni = params.peer;
+ ssm.tfo = params.tfo;
+ if (is_json(params.obfsParam)) {
+ const op_info = JSON.parse(params.obfsParam);
+ if (op_info.header) ssm.type = op_info.header;
+ if (op_info.Host) ssm.host = op_info.Host;
+ } else {
+ ssm.host = params.obfsParam;
+ }
+ ssm.allowinsecure = params.allowInsecure;
+ } else {
+ s.innerHTML = "<%:Invalid Share URL Format%>";
+ return false;
}
- var ssm = JSON.parse(sstr);
+
opt.set('remarks', ssm.ps);
opt.set(dom_prefix + 'address', unbracketIP(ssm.add));
opt.set(dom_prefix + 'port', ssm.port);
opt.set(dom_prefix + 'uuid', ssm.id);
opt.set(dom_prefix + 'security', ssm.security || ssm.scy || "auto");
- opt.set(dom_prefix + 'tls', ssm.tls === "tls");
- if (ssm.tls === "tls") {
+ opt.set(dom_prefix + 'tls', ssm.tls === "tls" || ssm.tls === "1");
+ if (ssm.tls === "tls" || ssm.tls === "1") {
opt.set(dom_prefix + 'tls_serverName', ssm.sni || ssm.host);
opt.set(dom_prefix + 'alpn', ssm.alpn || "default");
if (ssm.fp && ssm.fp !== "") {
@@ -1679,6 +1753,7 @@ local current_node = map:get(section)
dom_prefix + 'tls_allowInsecure',
!((queryParam.allowinsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0' && (queryParam.allow_insecure ?? '0') === '0')
);
+ opt.set(dom_prefix + 'tls_pinSHA256', queryParam.pcs || queryParam.pinSHA256 || '');
if (hash) {
opt.set('remarks', decodeURIComponent(hash.substr(1)));
}
@@ -1711,29 +1786,23 @@ local current_node = map:get(section)
}
queryParam.security = queryParam.security || "tls";
if (queryParam.security) {
+ opt.set(dom_prefix + 'tls', true);
if (queryParam.security == "tls") {
- opt.set(dom_prefix + 'tls', true);
opt.set(dom_prefix + 'reality', false);
- opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default');
- opt.set(dom_prefix + 'tls_serverName', queryParam.sni || queryParam.peer || '');
- opt.set(dom_prefix + 'tls_allowInsecure', !((queryParam.allowinsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0'));
- if (queryParam.fp && queryParam.fp.trim() != "") {
- opt.set(dom_prefix + 'utls', true);
- opt.set(dom_prefix + 'fingerprint', queryParam.fp);
- }
}
if (queryParam.security == "reality") {
- opt.set(dom_prefix + 'tls', true);
opt.set(dom_prefix + 'reality', true);
- opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default');
- opt.set(dom_prefix + 'tls_serverName', queryParam.sni || queryParam.peer || '');
- if (queryParam.fp && queryParam.fp.trim() != "") {
- opt.set(dom_prefix + 'utls', true);
- opt.set(dom_prefix + 'fingerprint', queryParam.fp);
- }
opt.set(dom_prefix + 'reality_publicKey', queryParam.pbk || '');
opt.set(dom_prefix + 'reality_shortId', queryParam.sid || '');
}
+ opt.set(dom_prefix + 'alpn', queryParam.alpn || 'default');
+ opt.set(dom_prefix + 'tls_serverName', queryParam.sni || queryParam.peer || '');
+ if (queryParam.fp && queryParam.fp.trim() != "") {
+ opt.set(dom_prefix + 'utls', true);
+ opt.set(dom_prefix + 'fingerprint', queryParam.fp);
+ }
+ opt.set(dom_prefix + 'tls_allowInsecure', !((queryParam.allowinsecure ?? '0') === '0' && (queryParam.insecure ?? '0') === '0'));
+ opt.set(dom_prefix + 'tls_pinSHA256', queryParam.pcs || queryParam.pinSHA256 || '');
}
if (m.hash) {
opt.set('remarks', decodeURIComponent(m.hash.substr(1)));
diff --git a/luci-app-passwall2/po/fa/passwall2.po b/luci-app-passwall2/po/fa/passwall2.po
index e34580383..4b928688b 100644
--- a/luci-app-passwall2/po/fa/passwall2.po
+++ b/luci-app-passwall2/po/fa/passwall2.po
@@ -2345,6 +2345,9 @@ msgstr "صرفنظر از گره %s، زیرا گرههای نوع %s به
msgid "%s type node subscriptions are not currently supported, skip this node."
msgstr "اشتراکهای گره نوع %s در حال حاضر پشتیبانی نمیشوند، صرفنظر از این گره."
+msgid "Skipping %s node. This node URI format cannot be parsed."
+msgstr "از گره %s صرفنظر میشود. فرمت URI این گره قابل تجزیه نیست."
+
msgid "Update [%s]"
msgstr "بهروزرسانی [%s]"
diff --git a/luci-app-passwall2/po/ru/passwall2.po b/luci-app-passwall2/po/ru/passwall2.po
index a9f8c5bcd..516944d66 100644
--- a/luci-app-passwall2/po/ru/passwall2.po
+++ b/luci-app-passwall2/po/ru/passwall2.po
@@ -2346,6 +2346,9 @@ msgstr "Пропуск сервера %s: серверы типа %s требу
msgid "%s type node subscriptions are not currently supported, skip this node."
msgstr "Подписки на серверы типа %s пока не поддерживаются, этот сервер пропускается."
+msgid "Skipping %s node. This node URI format cannot be parsed."
+msgstr "Пропуск узла %s. Формат URI этого узла не поддается разбору."
+
msgid "Update [%s]"
msgstr "Обновление [%s]"
diff --git a/luci-app-passwall2/po/zh-cn/passwall2.po b/luci-app-passwall2/po/zh-cn/passwall2.po
index 8e44b7dc4..cbd7365b7 100644
--- a/luci-app-passwall2/po/zh-cn/passwall2.po
+++ b/luci-app-passwall2/po/zh-cn/passwall2.po
@@ -2331,6 +2331,9 @@ msgstr "跳过 %s 节点,因 %s 类型的节点需要 Sing-Box 1.12 以上版
msgid "%s type node subscriptions are not currently supported, skip this node."
msgstr "暂时不支持 %s 类型的节点订阅,跳过此节点。"
+msgid "Skipping %s node. This node URI format cannot be parsed."
+msgstr "跳过 %s 节点,该节点 URI 格式无法解析。"
+
msgid "Update [%s]"
msgstr "更新【%s】"
diff --git a/luci-app-passwall2/po/zh-tw/passwall2.po b/luci-app-passwall2/po/zh-tw/passwall2.po
index 0a1338b08..61755c441 100644
--- a/luci-app-passwall2/po/zh-tw/passwall2.po
+++ b/luci-app-passwall2/po/zh-tw/passwall2.po
@@ -2337,6 +2337,9 @@ msgstr "跳過 %s 節點,因 %s 類型的節點需要 Sing-Box 1.12 以上版
msgid "%s type node subscriptions are not currently supported, skip this node."
msgstr "暫時不支持 %s 類型的節點訂閱,跳過此節點。"
+msgid "Skipping %s node. This node URI format cannot be parsed."
+msgstr "跳過 %s 節點,該節點 URI 格式無法解析。"
+
msgid "Update [%s]"
msgstr "更新【%s】"
diff --git a/luci-app-passwall2/root/usr/share/passwall2/iptables.sh b/luci-app-passwall2/root/usr/share/passwall2/iptables.sh
index b6c3572e9..aad5cae38 100755
--- a/luci-app-passwall2/root/usr/share/passwall2/iptables.sh
+++ b/luci-app-passwall2/root/usr/share/passwall2/iptables.sh
@@ -26,7 +26,7 @@ ip6t_m="$ip6t -t mangle -w"
[ -z "$ip6t" -o -z "$(lsmod | grep 'ip6table_mangle')" ] && ip6t_m="eval #$ip6t_m"
FWI=$(uci -q get firewall.passwall2.path 2>/dev/null)
FAKE_IP="198.18.0.0/16"
-FAKE_IP_6="fc00::/18"
+FAKE_IP_6="2001:2::/48"
factor() {
if [ -z "$1" ] || [ -z "$2" ]; then
@@ -627,21 +627,25 @@ filter_direct_node_list() {
update_wan_sets() {
[ -z "$(command -v get_wan_ips)" ] && . "$UTILS_PATH"
- local WAN_IP=$(get_wan_ips ip4)
- [ -n "$WAN_IP" ] && {
- ipset -F "$IPSET_WAN"
- for wan_ip in $WAN_IP; do
- ipset -! add "$IPSET_WAN" "$wan_ip"
- done
- }
+ (
+ flock -x 9 || exit 1
- local WAN6_IP=$(get_wan_ips ip6)
- [ -n "$WAN6_IP" ] && {
- ipset -F "$IPSET_WAN6"
- for wan6_ip in $WAN6_IP; do
- ipset -! add "$IPSET_WAN6" "$wan6_ip"
- done
- }
+ local WAN_IP=$(get_wan_ips ip4)
+ [ -n "$WAN_IP" ] && {
+ # ipset -F "$IPSET_WAN"
+ for wan_ip in $WAN_IP; do
+ ipset -! add "$IPSET_WAN" "$wan_ip"
+ done
+ }
+
+ local WAN6_IP=$(get_wan_ips ip6)
+ [ -n "$WAN6_IP" ] && {
+ # ipset -F "$IPSET_WAN6"
+ for wan6_ip in $WAN6_IP; do
+ ipset -! add "$IPSET_WAN6" "$wan6_ip"
+ done
+ }
+ ) 9>"${LOCK_PATH}/${CONFIG}_update_wan_sets.lock"
}
add_firewall_rule() {
diff --git a/luci-app-passwall2/root/usr/share/passwall2/nftables.sh b/luci-app-passwall2/root/usr/share/passwall2/nftables.sh
index e282d98d5..53f2f3c0f 100755
--- a/luci-app-passwall2/root/usr/share/passwall2/nftables.sh
+++ b/luci-app-passwall2/root/usr/share/passwall2/nftables.sh
@@ -18,7 +18,7 @@ FWMARK="0x50535732"
FWI=$(uci -q get firewall.passwall2.path 2>/dev/null)
FAKE_IP="198.18.0.0/16"
-FAKE_IP_6="fc00::/18"
+FAKE_IP_6="2001:2::/48"
factor() {
local ports="$1"
@@ -686,17 +686,21 @@ mwan3_start() {
update_wan_sets() {
[ -z "$(command -v get_wan_ips)" ] && . "$UTILS_PATH"
- local WAN_IP=$(get_wan_ips ip4)
- [ -n "$WAN_IP" ] && {
- nft flush set $NFTABLE_NAME $NFTSET_WAN
- echo "$WAN_IP" | insert_nftset $NFTSET_WAN "-1"
- }
+ (
+ flock -x 9 || exit 1
- local WAN6_IP=$(get_wan_ips ip6)
- [ -n "${WAN6_IP}" ] && {
- nft flush set $NFTABLE_NAME $NFTSET_WAN6
- echo "$WAN6_IP" | insert_nftset $NFTSET_WAN6 "-1"
- }
+ local WAN_IP=$(get_wan_ips ip4)
+ [ -n "$WAN_IP" ] && {
+ # nft flush set $NFTABLE_NAME $NFTSET_WAN
+ echo "$WAN_IP" | insert_nftset $NFTSET_WAN
+ }
+
+ local WAN6_IP=$(get_wan_ips ip6)
+ [ -n "${WAN6_IP}" ] && {
+ # nft flush set $NFTABLE_NAME $NFTSET_WAN6
+ echo "$WAN6_IP" | insert_nftset $NFTSET_WAN6
+ }
+ ) 9>"${LOCK_PATH}/${CONFIG}_update_wan_sets.lock"
}
add_firewall_rule() {
diff --git a/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua b/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua
index ea10d1fbb..9e777cf98 100755
--- a/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua
+++ b/luci-app-passwall2/root/usr/share/passwall2/subscribe.lua
@@ -900,7 +900,6 @@ local function processData(szType, content, add_mode, group, sub_cfg)
-- if ssr_group then result.ssr_group = ssr_group end
result.remarks = base64Decode(params.remarks)
elseif szType == 'vmess' then
- local info = jsonParse(content)
if sub_vmess_type == "sing-box" and has_singbox then
result.type = 'sing-box'
elseif sub_vmess_type == "xray" and has_xray then
@@ -909,6 +908,58 @@ local function processData(szType, content, add_mode, group, sub_cfg)
log(2, i18n.translatef("Skipping the %s node is due to incompatibility with the %s core program or incorrect node usage type settings.", "VMess", "VMess"))
return nil
end
+ -- vmess://base64(json)
+ local info = jsonParse(content)
+ if not info then
+ -- vmess://base64(auto:uuid@host:port)?tfo=1&remark=xxx&&alterId=0&obfs=websocket&path=%2F&obfsParam=host (obfs ~= ws obfsParam={})
+ if content:find("?", 1, true) then
+ info = {}
+ local Info = split(content:gsub("/%?", "?"), "%?")
+ local sp = split(base64Decode(Info[1]), "@")
+ local id_info = split(sp[1], ":")
+ info.security = (#id_info > 1 and id_info[1] ~= "") and id_info[1] or "auto"
+ info.id = id_info[#id_info]
+
+ local addr, port = sp[2], "443"
+ if api.is_ipv6addrport(addr) then
+ local a, p = addr:match("^%[(.+)%]:(%d+)$")
+ if a then addr, port = a, p end
+ addr = api.get_ipv6_only(addr)
+ else
+ local host_port = split(addr, ":")
+ addr = host_port[1]
+ if #host_port > 1 then port = host_port[#host_port] end
+ end
+ info.add, info.port = addr, port
+
+ local params = {}
+ for _, v in pairs(split(Info[2], '&')) do
+ local s = v:find("=", 1, true)
+ if s and s > 1 then
+ params[v:sub(1, s - 1)] = UrlDecode(v:sub(s + 1))
+ end
+ end
+ info.ps = params.remark or params.remarks
+ info.net = (params.obfs == "websocket") and "ws" or (params.obfs or "tcp")
+ info.path = params.path
+ info.aid = params.alterId or "0"
+ info.tls = params.tls
+ info.sni = params.peer
+ info.tfo = params.tfo
+ local op_info = jsonParse(params.obfsParam)
+ if op_info then
+ if op_info.header then info.type = op_info.header end
+ if op_info.Host then info.host = op_info.Host end
+ else
+ info.host = params.obfsParam
+ end
+ info.allowinsecure = params.allowInsecure
+ else
+ log(2, i18n.translatef("Skipping %s node. This node URI format cannot be parsed."))
+ return nil
+ end
+ end
+
result.address = info.add
result.port = info.port
result.protocol = 'vmess'
@@ -1068,6 +1119,11 @@ local function processData(szType, content, add_mode, group, sub_cfg)
if idx_pn then
result.plugin = plugin_info:sub(1, idx_pn - 1)
result.plugin_opts = plugin_info:sub(idx_pn + 1, #plugin_info)
+ -- Some ShadowTLS subscriptions utilize SIP003.
+ result.plugin_opts = result.plugin_opts:gsub("^password=", "passwd=")
+ result.plugin_opts = result.plugin_opts:gsub(";password=", ";passwd=")
+ result.plugin_opts = result.plugin_opts:gsub("^version=([123])", "v%1=1")
+ result.plugin_opts = result.plugin_opts:gsub(";version=([123])", ";v%1=1")
else
result.plugin = plugin_info
end
@@ -1174,10 +1230,22 @@ local function processData(szType, content, add_mode, group, sub_cfg)
result.plugin_opts = nil
end
elseif result.type == 'sing-box' then
- if result.plugin ~= "obfs-local" and result.plugin ~= "v2ray-plugin" then
+ if result.plugin ~= "obfs-local" and result.plugin ~= "v2ray-plugin" and result.plugin ~= "shadow-tls" then
result.error_msg = i18n.translatef("%s unsupport SS %s plugin.", "Sing-Box", result.plugin)
else
result.plugin_enabled = "1"
+ -- Some ShadowTLS subscriptions utilize SIP003.
+ if result.plugin == "shadow-tls" then
+ for item in result.plugin_opts:gmatch("[^;]+") do
+ local key, value = item:match("^([^=]+)=(.*)$")
+ if key == "host" then result.shadowtls_serverName = value end
+ if key == "passwd" then result.shadowtls_password = value end
+ if key:match("^v[123]$") then result.shadowtls_version = key:sub(2) end
+ end
+ result.shadowtls = "1"
+ result.plugin_opts = nil
+ result.plugin_enabled = nil
+ end
end
else
result.plugin_enabled = "1"
@@ -1722,6 +1790,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
result.hysteria_auth_type = "string"
result.hysteria_auth_password = params.auth
result.tls_serverName = params.peer or params.sni or ""
+ result.tls_pinSHA256 = params.pcs or params.pinSHA256
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")
result.alpn = params.alpn
@@ -1845,6 +1914,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
end
result.tls_serverName = params.sni
result.tls_disable_sni = params.disable_sni
+ result.tls_pinSHA256 = params.pcs or params.pinsha256
result.tuic_alpn = params.alpn or "h3"
result.tuic_congestion_control = params.congestion_control or "cubic"
result.tuic_udp_relay_mode = params.udp_relay_mode or "native"
@@ -1900,6 +1970,7 @@ local function processData(szType, content, add_mode, group, sub_cfg)
if params.security == "tls" or params.security == "reality" then
result.tls = "1"
result.tls_serverName = params.sni or params.peer
+ result.tls_pinSHA256 = params.pcs or params.pinsha256
result.alpn = params.alpn
if params.fp and params.fp ~= "" then
result.utls = "1"
@@ -2063,6 +2134,9 @@ local function curl(url, file, ua, mode, hwid)
if hwid == "1" then
curl_args[#curl_args + 1] = get_headers()
end
+ local cookie_file = "/tmp/cookie_" .. api.gen_random_char(5)
+ curl_args[#curl_args + 1] = '-c "' .. cookie_file .. '" -b "' .. cookie_file .. '"'
+
local return_code, result
if mode == "direct" then
return_code, result = api.curl_direct(url, file, curl_args)
@@ -2071,6 +2145,8 @@ local function curl(url, file, ua, mode, hwid)
else
return_code, result = api.curl_auto(url, file, curl_args)
end
+ luci.sys.call('rm -f "%s"' % cookie_file)
+
return return_code, tonumber(result)
end
@@ -2599,7 +2675,7 @@ local execute = function()
f:close()
local raw_data = api.trim(stdout)
local old_md5 = value.md5 or ""
- local new_md5 = luci.sys.exec("md5sum " .. tmp_file .. " 2>/dev/null | awk '{print $1}'"):gsub("\n", "")
+ local new_md5 = luci.sys.exec("md5sum " .. tmp_file .. " 2>/dev/null | awk '{printf \"%s\", $1}'")
if not manual_sub and old_md5 == new_md5 then
log(1, i18n.translatef("Subscription: [%s] No changes, no update required.", remark))
else
diff --git a/luci-app-wizard/htdocs/luci-static/resources/view/wizard/initsetup.js b/luci-app-wizard/htdocs/luci-static/resources/view/wizard/initsetup.js
index 564808826..aa18812ae 100644
--- a/luci-app-wizard/htdocs/luci-static/resources/view/wizard/initsetup.js
+++ b/luci-app-wizard/htdocs/luci-static/resources/view/wizard/initsetup.js
@@ -135,7 +135,7 @@ return view.extend({
ipv6: uci.get('network', 'wan6', 'auto') === '0' ? '0' : '1',
https: uci.get('wizard', 'default', 'https') || '0',
cookie_p: uci.get('wizard', 'default', 'persistent_cookies') || '1',
- landing_page: uci.get('wizard', 'default', 'landing_page') || 'default',
+ landing_page: uci.get('wizard', 'default', 'landing_page') || 'auto',
autoupgrade_fm: uci.get('wizard', 'default', 'autoupgrade_fm') || '0',
coremark: uci.get('wizard', 'default', 'coremark') || '0',
wifi_ssid: baseSsid || (this.hasWireless ? 'Kwrt' : ''),
@@ -195,7 +195,7 @@ return view.extend({
{ tab: 'firmware', type: form.Flag, id: 'cookie_p', title: _('Persistent Cookie Session'), desc: _('Maintain persistent login sessions in the web browser.') },
{ tab: 'firmware', type: form.Flag, id: 'https', title: _('Enforce HTTPS Access'), desc: _('Automatically redirect HTTP requests to secure HTTPS.') },
{ tab: 'firmware', type: form.ListValue, id: 'landing_page', title: _('Landing Dashboard Mode'),
- choices: { 'default': _('Default'), 'dashboard': _('Dashboard'), routerdog: _('RouterDog'), nas: _('NAS'), 'next-nas': _('Next-NAS'), router: _('Router') } }
+ choices: { 'auto': _('Auto'), 'overview': _('Overview'), 'istoreos': _('iStoreOS'), 'dashboard': _('Dashboard'), routerdog: _('RouterDog'), nas: _('NAS'), 'next-nas': _('Next-NAS'), router: _('Router') } }
];
// 无线配置根据设备硬件动态追加
diff --git a/luci-app-wizard/luasrc/controller/wizard.lua b/luci-app-wizard/luasrc/controller/wizard.lua
index 5365e8a03..04890f932 100644
--- a/luci-app-wizard/luasrc/controller/wizard.lua
+++ b/luci-app-wizard/luasrc/controller/wizard.lua
@@ -1,40 +1,67 @@
module("luci.controller.wizard", package.seeall)
-local uci = luci.model.uci.cursor()
local http = require "luci.http"
function index()
- entry({"admin", "index"}, call("landing_page"), _("Home") , 0).dependent = false
+ entry({"admin", "index"}, call("landing_page"), _("Home"), 0).dependent = false
- -- 2. 核心:直接改写 admin 根节点的调度目标
- -- 这样当用户访问 /admin、登录后跳转或访问根目录时,无需经过 firstchild(),直接执行 landing_page()
+ -- 直接改写 admin 根节点的调度目标
local admin = node("admin")
if admin then
admin.target = call("landing_page")
end
end
+local function check_wifi(uci)
+ local has_wifi = false
+ pcall(function()
+ uci:foreach("wireless", "wifi-device", function(s)
+ has_wifi = true
+ return false -- 检测到任意 wifi-device 即终止遍历
+ end)
+ end)
+ return has_wifi
+end
+
+local function is_running(proc)
+ return luci.sys.call(string.format("pgrep %s >/dev/null", proc)) == 0
+end
+
function landing_page()
- local landing_page = uci:get("wizard", "default", "landing_page")
- if (landing_page == "default") then
- http.redirect(luci.dispatcher.build_url("admin","status","overview"))
- elseif (landing_page == "dashboard") then
- http.redirect(luci.dispatcher.build_url("admin","status","dashboard"))
- elseif (luci.sys.call("pgrep routergo >/dev/null") == 0 and landing_page == "routerdog") then
- http.redirect(luci.dispatcher.build_url("admin","routerdog"));
- elseif luci.sys.call("pgrep quickstart >/dev/null") == 0 then
- if landing_page == "nas" then
- http.redirect(luci.dispatcher.build_url("admin","istorex","nas"));
- elseif landing_page == "next-nas" then
- http.redirect(luci.dispatcher.build_url("admin","istorex","next-nas"));
- elseif landing_page == "router" then
- http.redirect(luci.dispatcher.build_url("admin","istorex","router"));
- else
- http.redirect(luci.dispatcher.build_url("admin","quickstart"));
- end
- else
- http.redirect(luci.dispatcher.build_url("admin", "status", "overview"))
+ local uci = luci.model.uci.cursor()
+ local page = uci:get("wizard", "default", "landing_page")
+ local target = {"admin", "status", "overview"}
+
+ -- 1. 默认/自动模式
+ if not page or page == "auto" then
+ if is_running("quickstart") then
+ target = {"admin", "quickstart"}
+ elseif check_wifi(uci) then
+ target = {"admin", "status", "dashboard"}
+ end
+
+ -- 2. 常规指定页面
+ elseif page == "overview" then
+ target = {"admin", "status", "overview"}
+ elseif page == "dashboard" then
+ target = {"admin", "status", "dashboard"}
+
+ -- 3. Routerdog 模式(优先比对字符串,命中再检测进程)
+ elseif page == "routerdog" and is_running("routergo") then
+ target = {"admin", "routerdog"}
+
+ -- 4. iStoreOS / iStoreX 增强页面(仅在 quickstart 运行时有效)
+ elseif is_running("quickstart") then
+ local istore_routes = {
+ istoreos = {"admin", "quickstart"},
+ nas = {"admin", "istorex", "nas"},
+ ["next-nas"] = {"admin", "istorex", "next-nas"},
+ router = {"admin", "istorex", "router"}
+ }
+ if istore_routes[page] then
+ target = istore_routes[page]
end
end
-
+
+ http.redirect(luci.dispatcher.build_url(unpack(target)))
end