From d45a1cb4ad3eb4b55ff980073a73adbd0b40c41d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 1 Oct 2026 00:51:52 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8D=93=20Sync=202026-10-01=2000:51:52?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- luci-app-homeproxy-pro/Makefile | 2 +- luci-app-homeproxy-pro/README.md | 3 + .../luci-static/resources/homeproxy-pro.js | 29 + .../resources/view/homeproxy-pro/client.js | 13 +- .../view/homeproxy-pro/client/access.js | 5 +- .../view/homeproxy-pro/client/common.js | 37 +- .../view/homeproxy-pro/client/dns.js | 164 ++-- .../view/homeproxy-pro/client/nodes.js | 10 +- .../view/homeproxy-pro/client/routing.js | 10 +- .../view/homeproxy-pro/client/subscription.js | 13 +- .../resources/view/homeproxy-pro/status.js | 35 + .../po/templates/homeproxy-pro.pot | 726 +++++++++-------- .../po/zh_Hans/homeproxy-pro.po | 746 ++++++++++-------- .../homeproxy-pro/scripts/firewall_post.ut | 34 +- .../etc/homeproxy-pro/scripts/fw4_names.sh | 1 + .../homeproxy-pro/scripts/generate_client.uc | 18 +- .../homeproxy-pro/scripts/generator/common.uc | 9 + .../scripts/generator/context.uc | 18 +- .../homeproxy-pro/scripts/generator/dns.uc | 153 +++- .../scripts/generator/outbound.uc | 4 +- .../homeproxy-pro/scripts/generator/route.uc | 86 +- .../scripts/generator/ruleset.uc | 55 +- .../homeproxy-pro/scripts/runtime/config.sh | 26 + .../etc/homeproxy-pro/scripts/runtime/dns.sh | 20 +- .../root/etc/init.d/homeproxy-pro | 34 +- .../rpcd/acl.d/luci-app-homeproxy-pro.json | 1 + .../usr/share/rpcd/ucode/luci.homeproxy-pro | 53 ++ .../fixtures/generators/dns_action_guards.uci | 51 ++ .../tests/fixtures/generators/dns_headers.uci | 33 + .../generators/routing_rule_disabled_node.uci | 39 + .../routing_rule_route_options_empty.uci | 29 + .../generators/routing_rule_tls_fragment.uci | 32 + .../ruleset_remote_bad_initial_path.uci | 38 + .../generators/ruleset_remote_preset.uci | 51 ++ .../ruleset_remote_update_interval.uci | 38 + .../tests/fixtures/runtime/trace.golden.txt | 28 + .../tests/frontend-validators.js | 30 + .../tests/runtime/test_config_transaction.sh | 24 + .../tests/runtime/test_dns_snippets.sh | 16 + .../tests/snapshots/client.json | 396 +++++----- .../tests/ucode/test_firewall_template.sh | 30 + .../tests/ucode/test_generators.sh | 225 +++++- .../tests/ucode/test_rpc_methods.sh | 6 +- luci-app-passwall2/Makefile | 4 +- .../client/node_subscribe_config.lua | 7 +- .../cbi/passwall2/client/type/1_sing-box.lua | 10 +- luci-app-passwall2/luasrc/passwall2/api.lua | 40 +- .../luasrc/passwall2/util_sing-box.lua | 20 +- .../luasrc/passwall2/util_xray.lua | 7 +- .../view/passwall2/node_config/header.htm | 2 +- .../passwall2/node_config/link_share_man.htm | 129 ++- luci-app-passwall2/po/fa/passwall2.po | 3 + luci-app-passwall2/po/ru/passwall2.po | 3 + luci-app-passwall2/po/zh-cn/passwall2.po | 3 + luci-app-passwall2/po/zh-tw/passwall2.po | 3 + .../root/usr/share/passwall2/iptables.sh | 34 +- .../root/usr/share/passwall2/nftables.sh | 26 +- .../root/usr/share/passwall2/subscribe.lua | 82 +- .../resources/view/wizard/initsetup.js | 4 +- luci-app-wizard/luasrc/controller/wizard.lua | 75 +- 60 files changed, 2630 insertions(+), 1193 deletions(-) create mode 100644 luci-app-homeproxy-pro/tests/fixtures/generators/dns_action_guards.uci create mode 100644 luci-app-homeproxy-pro/tests/fixtures/generators/dns_headers.uci create mode 100644 luci-app-homeproxy-pro/tests/fixtures/generators/routing_rule_disabled_node.uci create mode 100644 luci-app-homeproxy-pro/tests/fixtures/generators/routing_rule_route_options_empty.uci create mode 100644 luci-app-homeproxy-pro/tests/fixtures/generators/routing_rule_tls_fragment.uci create mode 100644 luci-app-homeproxy-pro/tests/fixtures/generators/ruleset_remote_bad_initial_path.uci create mode 100644 luci-app-homeproxy-pro/tests/fixtures/generators/ruleset_remote_preset.uci create mode 100644 luci-app-homeproxy-pro/tests/fixtures/generators/ruleset_remote_update_interval.uci 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 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 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