🤞 Sync 2026-09-30 23:44:05

This commit is contained in:
github-actions[bot] committed 2026-09-30 23:44:05 +08:00
1 parent e3fde93105
commit 44bb740ab2
206 files changed
+5736 -2799

No files matched your search

@@ -26,15 +26,6 @@ config homeproxy-pro 'config'
option main_udp_node 'same'
option dns_server '8.8.8.8'
option china_dns_server '223.5.5.5'
# Bootstrap resolver for the DoH/DoT endpoint's own hostname. Without
# it `main-dns` borrows the WAN resolver, so the one lookup that decides
# where every proxied name goes is answered by the ISP: poisoning that
# single answer is enough to break the whole DNS split, and it is the
# only query that was still leaving through the ISP after the split
# itself was configured correctly. This is a conffile, so upgrading
# never rewrites an existing /etc/config/homeproxy-pro - the value below
# only reaches fresh installs.
list bootstrap_dns '223.5.5.5'
option routing_mode 'bypass_mainland_china'
option routing_port 'common'
option proxy_mode 'redirect_tproxy'
@@ -385,7 +385,6 @@ export const Loader = {
/* A2.1: scalar + list fields on the `config` section. */
dns_server: opt(uci, 'main', 'dns_server'),
china_dns_server: opt(uci, 'main', 'china_dns_server'),
bootstrap_dns: opt(uci, 'main', 'bootstrap_dns') || [],
log_level: opt(uci, 'main', 'log_level') || 'warn',
tun_dns_mode: opt(uci, 'main', 'tun_dns_mode'),
tun_dns_address: opt(uci, 'main', 'tun_dns_address'),
@@ -94,26 +94,6 @@ export function build_context(dm, env) {
? env.wan_dns
: ((routing_mode in ['proxy_mainland_china', 'global']) ? '8.8.8.8' : '223.5.5.5');
/* Bootstrap resolvers: the servers the proxy path uses to look up a DNS
* server's *own* hostname. `main-dns` is the user's DoH/DoT endpoint
* and the only server whose address may be a name, so this lookup has to
* work before any configured resolver can answer.
*
* It used to be borrowed from default-dns (the WAN/ISP resolver), which
* is the one resolver a polluted or unreachable WAN takes down; the
* option lets the user name a resolver that still answers. Empty means
* "keep using the WAN resolver", so an untouched configuration
* generates exactly the same bytes as before. */
const bootstrap_dns = [];
if (type(dm.general.bootstrap_dns) === 'array') {
for (let server in dm.general.bootstrap_dns)
if (!isEmpty(server))
push(bootstrap_dns, server);
} else if (!isEmpty(dm.general.bootstrap_dns)) {
/* UCI hands back a bare string for a list with a single value. */
push(bootstrap_dns, dm.general.bootstrap_dns);
}
/* dns/routing fields used by both sections */
const dns_optimistic_cache = (dm.dns.settings || {}).optimistic_cache || '0';
const dns_optimistic_timeout = (dm.dns.settings || {}).optimistic_timeout;
@@ -157,7 +137,7 @@ export function build_context(dm, env) {
tcpip_stack, endpoint_independent_nat,
udp_timeout, udp_mapping, udp_filtering, udp_nat_max,
tun_dns_mode, tun_dns_address,
wan_dns, bootstrap_dns, dns_optimistic_cache, dns_optimistic_timeout,
wan_dns, dns_optimistic_cache, dns_optimistic_timeout,
dns_query_timeout, dns_store_dns,
default_interface: (dm.access_control.control || {}).bind_interface,
@@ -92,15 +92,69 @@ const NAPTR_BYPASS_SUFFIXES = [
'pub.3gppnetwork.org'
];
/* The address of the bootstrap resolver, or null when there is none.
*
* Every DNS server whose address is a *hostname* needs a domain_resolver
* before sing-box will start it, and that lookup is the one step nothing
* else can bootstrap: it has to work before any of the configured servers
* can answer. `main-dns` is exactly that case - it is the user's DoH/DoT
* endpoint - and it used to borrow default-dns (the WAN/ISP resolver) for
* it, which is the single resolver a polluted or unreachable WAN takes down.
*
* It used to be a field of its own in the form, which made no sense to read:
* a second DNS address next to the China DNS server, both of them a bare
* public IP, for a lookup that happens once at startup. It is derived now,
* from the China DNS server, which is the one address this router already
* knows it can reach directly. That answer is only usable when it is a bare
* IP: the option also accepts the literal 'wan' and a DoH/DoT URL, and both
* of those are hostnames - a hostname cannot resolve the hostname it would be
* needed to resolve. For those, and only for those, this returns null and
* main-dns keeps borrowing the WAN resolver, exactly as it did before any of
* this existed.
*/
function bootstrap_addr(ctx) {
const addr = ctx.china_dns_server;
if (isEmpty(addr))
return null;
return (match(addr, /^[0-9]+(\.[0-9]+){3}$/) != null
|| match(addr, /^[0-9a-fA-F:]+$/) != null) ? addr : null;
}
/* Append the bootstrap resolver, when there is a usable address for it.
*
* The bootstrap server carries a plain IP, has no domain_resolver of its own
* and is never referenced by a DNS rule: it exists only as the target of the
* domain_resolver pointer, so there is no cycle for sing-box to reject.
* Nothing is emitted when there is no such address ("resolve through the WAN
* resolver"), which keeps a configuration whose China DNS server is a
* hostname byte-identical to one that never had this.
*
* It is emitted only alongside main-dns: without a node there is no DoH/DoT
* endpoint to bootstrap, and a server nothing points at is just noise. */
function append_bootstrap_dns(config, ctx) {
const addr = bootstrap_addr(ctx);
if (isEmpty(addr) || isEmpty(ctx.main_node))
return;
push(config.dns.servers, {
tag: 'bootstrap-dns',
type: 'udp',
server: addr,
detour: ctx.self_mark ? 'direct-out' : null
});
}
function append_proxy_dns(config, dm, ctx) {
if (isEmpty(ctx.main_node))
return;
/* The user's DoH/DoT endpoint is the one server in this block whose
* address is a hostname, so it is the one that needs the bootstrap
* resolver; without a configured one it keeps resolving through
* default-dns (the WAN resolver), exactly as before. */
const main_resolver = isEmpty(ctx.bootstrap_dns) ? 'default-dns' : 'bootstrap-dns';
* resolver. Which resolver that is follows the China DNS server - the
* one direct-routable address already configured for this router - and
* only when it is a bare IP; see bootstrap_addr(). */
const main_resolver = isEmpty(bootstrap_addr(ctx)) ? 'default-dns' : 'bootstrap-dns';
/* Main DNS */
push(config.dns.servers, {
@@ -484,39 +538,6 @@ function initDns(config, ctx) {
};
}
/* Append the bootstrap resolver, if the user configured one.
*
* Every DNS server whose address is a *hostname* needs a domain_resolver
* before sing-box will start it, and that lookup is the one step nothing
* else can bootstrap: it has to work before any of the configured servers
* can answer. `main-dns` is exactly that case - it is the user's DoH/DoT
* endpoint - and it used to borrow default-dns (the WAN/ISP resolver) for
* it, which is the single resolver a polluted or unreachable WAN takes down.
*
* The bootstrap server carries a plain IP, has no domain_resolver of its own
* and is never referenced by a DNS rule: it exists only as the target of the
* domain_resolver pointer, so there is no cycle for sing-box to reject.
* Nothing is emitted when the list is empty ("resolve through the WAN
* resolver"), which keeps an untouched configuration byte-identical.
*
* Only the first entry is used. sing-box resolves a hostname through
* exactly one transport (`DomainResolveOptions.Server` is a single tag), and
* registering extra servers would not make them a fallback chain - it would
* only leave unused servers in the config. The list form is kept because
* the option is a DynamicList in the UI and a value with several entries
* must not produce an unparseable address. */
function append_bootstrap_dns(config, ctx) {
if (isEmpty(ctx.bootstrap_dns))
return;
push(config.dns.servers, {
tag: 'bootstrap-dns',
type: 'udp',
server: ctx.bootstrap_dns[0],
detour: ctx.self_mark ? 'direct-out' : null
});
}
/* Build the sing-box `dns` block. Mutates `config` and returns it for
* chaining. Routing mode is determined by ctx.routing_mode: any non-
* 'custom' mode follows the proxy path (the proxy-mode split inside the