🤞 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

+1 -1
View File
@@ -282,7 +282,7 @@ Two further host differences are bridged so the remaining checks still run:
| `tests/ucode/test_parser_flatten.uc` | Round-trip invariant for the canonical-Node pipeline: `flatten(normalize(parse_uri(uri)))` must equal the parser's flat UCI dict field-for-field for every supported scheme. This is what stops the parser, the Loader's `PROTOCOL_OPTIONS` (now derived from `parser/mapping.uc`) and the Repository's flatten from drifting apart. |
| `tests/ucode/test_subscription_repository.uc` | Integration test for `subscription/repository.uc`: stages a sandboxed UCI file with a user node, a kept subscription node, and a dropped subscription node, then runs `Repository.apply_nodes` and asserts (a) the user node is untouched, (b) the kept node has the new fields and no stale fields, (c) the dropped node is gone, (d) a brand-new node is added under `md5(group+label)`. PR-03 added the `apply_main_node_refs` paths (urltest prune + missing-target switch + reset-to-nil) and the `scrub_stale_urltest_refs` pass to the same test. |
| `tests/ucode/test_generators.sh` | Runs `generate_client.uc`/`generate_server.uc` against the UCI fixtures in `tests/fixtures/generators/` inside a scratch directory (the `uci` cursor and `HP_DIR`/`RUN_DIR` are redirected) and validates each result with `sing-box check`. The `redirect` case is the product's default `proxy_mode` and asserts, from the emitted JSON, that every dialling outbound carries the runtime `routing_mark`. |
| `tests/ucode/test_dns_proxy_list.sh` | The `proxy_list.txt` → `dns.rules` plumbing and the bootstrap resolver, neither of which `run_case()` in the generator suite can see (it truncates both resource lists before every case and the fixture has no `bootstrap_dns` option). Patches the client fixture to each of the four proxy modes with both lists populated, and asserts the DNS rule order (`direct-domain` < `proxy-domain` via `main-dns` < the SVCB/HTTPS reject, `geosite-cn` last when the mode emits it) plus the routing half's `"tag": "proxy-domain"` and `route.final: main-out`. Then, separately: with `bootstrap_dns` set, `main-dns` must resolve its own hostname through `bootstrap-dns` and that server must carry no `domain_resolver`; with it unset, no `bootstrap-dns` server may be emitted at all. |
| `tests/ucode/test_dns_proxy_list.sh` | The `proxy_list.txt` → `dns.rules` plumbing and the bootstrap resolver, neither of which `run_case()` in the generator suite can see (it truncates both resource lists before every case and the fixture has no `china_dns_server` option). Patches the client fixture to each of the four proxy modes with both lists populated, and asserts the DNS rule order (`direct-domain` < `proxy-domain` via `main-dns` < the SVCB/HTTPS reject, `geosite-cn` last when the mode emits it) plus the routing half's `"tag": "proxy-domain"` and `route.final: main-out`. Then, separately, the bootstrap resolver: it is derived from `china_dns_server`, so a bare IP there must make `main-dns` resolve its own hostname through `bootstrap-dns` with no `domain_resolver` of its own, and a DoH URL there (a hostname, which cannot resolve a hostname) must emit no `bootstrap-dns` server at all and leave `main-dns` on `default-dns`. |
| `tests/toolchain/build-ucode-linux.sh` | Builds the local ucode toolchain described above; not part of `tests/run.sh`. |
| `tests/toolchain/validate-data.sh` | Off-target stand-in for `/sbin/validate_data`, used through `HP_VALIDATE_DATA`; not part of `tests/run.sh`. |
| `tests/ucode/test_ucode_grammar.sh` | Pins the ucode dialect: the toolchain must reject `export function ... }` without `;` and object/array destructuring, and must accept the constructs the package uses (`?.`/`??`, object spread, computed keys, template literals). |
+37 -20
View File
@@ -2065,31 +2065,48 @@ rm -rf "$SNAP_TMP"
trap - EXIT INT TERM
echo
echo "== guard 45: a fresh install bootstraps main-dns, an untouched config still does not =="
echo "== guard 45: the bootstrap resolver is derived, not configured =="
# E3. With no bootstrap_dns, `main-dns` (the DoH/DoT endpoint) falls back to
# the WAN resolver, so the ISP answers the one lookup that decides where every
# proxied name goes. The package default below closes that for fresh
# installs; this guard exists so neither half drifts silently:
# (a) an explicitly empty list must still emit NO bootstrap-dns server, so
# a user who clears the field gets the old behaviour back rather than a
# resolver the generator invents for them;
# (b) the package-shipped default is what makes the fix reach a new install
# at all - the field is read from the 'config' section, and the file is
# a conffile, so this line only lands on fresh installs.
if grep -q "if (isEmpty(ctx.bootstrap_dns))" "$SCRIPTS/generator/dns.uc"; then
pass "an empty bootstrap_dns still emits no bootstrap-dns server"
# main-dns is the user's DoH/DoT endpoint and the one server in the config
# whose address is a hostname, so it needs a domain_resolver before it can
# answer. It used to borrow default-dns - the WAN/ISP resolver - which makes
# the ISP the answerer of the single query that decides where every proxied
# name goes. It used to be a form field for that; it is derived now, from the
# China DNS server. Both halves are pinned:
# (a) the generator must read the China DNS server, never a separate option
# - a second DNS address next to the China one made no sense to read;
# (b) a bare IP is the only usable value, because the option also accepts
# 'wan' and a DoH/DoT URL, and a hostname cannot resolve a hostname.
if grep -q "const addr = ctx.china_dns_server;" "$SCRIPTS/generator/dns.uc"; then
pass "the bootstrap address is taken from the China DNS server"
else
fail "append_bootstrap_dns no longer short-circuits on an empty bootstrap_dns - a"
fail "user who cleared the field would get a resolver they did not ask for"
fail "bootstrap_addr() no longer reads ctx.china_dns_server - if it reads a"
fail "separate option again, main-dns may fall back to the WAN resolver"
fi
if grep -q "^ list bootstrap_dns '223.5.5.5'" "$ROOT/root/etc/config/homeproxy-pro"; then
pass "a fresh install ships a bootstrap_dns, so main-dns never asks the ISP"
if grep -qE "match\(addr, /\^\[0-9\]\+" "$SCRIPTS/generator/dns.uc" &&
grep -qE "match\(addr, /\^\[0-9a-fA-F:\]\+" "$SCRIPTS/generator/dns.uc"; then
pass "only a bare IP is accepted as the bootstrap address"
else
fail "the package-shipped /etc/config/homeproxy-pro no longer ships a bootstrap_dns -"
fail "a new install would resolve its DoH endpoint's hostname through the WAN"
fail "resolver, which is the last query that still leaves via the ISP"
fail "bootstrap_addr() no longer rejects a non-IP value - a DoH URL or the"
fail "literal 'wan' is a hostname, and using one to resolve a hostname is a cycle"
fi
# The option is gone from the form and from the shipped config; leaving either
# would mean a value nothing reads, which is how the confusing second field
# came to exist in the first place.
if grep -q "bootstrap_dns" "$ROOT/root/etc/config/homeproxy-pro"; then
fail "the package-shipped /etc/config/homeproxy-pro still carries a bootstrap_dns -"
fail "the generator derives it now, so this option is read by nothing"
else
pass "the package-shipped /etc/config/homeproxy-pro carries no bootstrap_dns"
fi
if grep -rq "bootstrap_dns" "$ROOT/htdocs/luci-static/resources/view/homeproxy-pro/"; then
fail "a view still renders a bootstrap_dns field - the value is derived from"
fail "the China DNS server, and a second DNS address only reads as a mistake"
else
pass "no view offers a bootstrap_dns field any more"
fi
echo
@@ -3281,8 +3281,8 @@
{
"kind": "Value",
"name": "dns_server",
"title": "DNS server",
"description": "Resolves every domain that is not on the China DNS path, through the selected proxy node. Pick an overseas service: 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. Support UDP, TCP, DoH, DoQ, DoT. TCP protocol will be used if not specified.",
"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",
@@ -3290,10 +3290,6 @@
}
],
"values": [
[
"wan",
"WAN DNS (read from interface)"
],
[
"1.1.1.1",
"CloudFlare Public DNS (1.1.1.1)"
@@ -3305,29 +3301,13 @@
[
"8.8.8.8",
"Google Public DNS (8.8.8.8)"
],
[
"",
"---"
],
[
"223.5.5.5",
"Aliyun Public DNS (223.5.5.5)"
],
[
"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": "8.8.8.8",
"rmempty": false,
"validate": "fn(758:77dbe8107e5c)"
"validate": "fn(730:48f7f360fbd2)"
}
},
{
@@ -3387,24 +3367,6 @@
"rmempty": false
}
},
{
"kind": "DynamicList",
"name": "bootstrap_dns",
"title": "Bootstrap DNS server",
"description": "Resolver used to look up the DNS server's own hostname - only relevant when that address is a domain, as with a DoH/DoT endpoint. Leave empty to use the WAN resolver. Only the first entry is used.",
"depends": [
{
"routing_mode": "custom",
"!reverse": true
}
],
"values": [],
"subsection": null,
"props": {
"datatype": "ipaddr",
"modalonly": true
}
},
{
"kind": "SectionValue",
"name": "_dns_cache",
@@ -20,10 +20,13 @@
#
# 2. The bootstrap resolver: the DoH/DoT endpoint (main-dns) is the only
# server whose address is a hostname, and it used to borrow default-dns
# (the WAN/ISP resolver) for that lookup. When the user configures a
# bootstrap resolver, main-dns must point at it instead, and that server
# must stay at the bottom of the chain (no domain_resolver of its own,
# no DNS rule routing to it) or the lookup depends on itself.
# (the WAN/ISP resolver) for that lookup, which makes the ISP the
# answerer of the one query that decides where every proxied name goes.
# The resolver is derived from the China DNS server now, so both halves
# of that rule are asserted: a bare IP there yields a bootstrap-dns that
# main-dns points at and which carries no domain_resolver of its own,
# and a hostname there yields no bootstrap server at all (a hostname
# cannot resolve the hostname it would be needed to resolve).
#
# Usage: sh tests/ucode/test_dns_proxy_list.sh <repo-root> [work-dir]
@@ -34,7 +37,8 @@ ROOT="$(cd "$ROOT" && pwd)"
FIXTURE="$ROOT/tests/fixtures/generators/client.uci"
DIRECT_DOMAIN="direct.example.cn"
PROXY_DOMAIN="play.example.cn"
BOOTSTRAP_DNS="223.5.5.5"
CHINA_DNS_IP="223.5.5.5"
CHINA_DNS_DOH="https://dns.alidns.com/dns-query"
FAILED=0
rm -rf "$WORK"
@@ -42,12 +46,14 @@ mkdir -p "$WORK"
# Stage the checkout the way test_generators.sh does: HP_DIR/RUN_DIR/UCICONFIG_DIR
# are rewritten into the work dir and the fixture is copied in as the UCI
# config. The extra bootstrap line is appended to the `config` section here
# (the section's first named section), which is how the Loader reads it.
# config. The fixture carries no china_dns_server (the generator then falls
# back to the Aliyun public resolver), so the line is inserted into the
# `config` section here - the section the Loader reads it from. It has to be
# inserted rather than substituted for that reason.
stage_case() {
dir="$1"
label="$2"
bootstrap="$3"
china_dns="$3"
fixture="$4"
rm -rf "$dir"
@@ -55,9 +61,9 @@ stage_case() {
"$dir/scripts/parser" "$dir/resources"
cp "$fixture" "$dir/config/homeproxy-pro"
if [ -n "$bootstrap" ]; then
if [ -n "$china_dns" ]; then
sed -i "/^\toption routing_mode /i\\
\toption bootstrap_dns '$bootstrap'
\toption china_dns_server '$china_dns'
" "$dir/config/homeproxy-pro"
fi
@@ -237,18 +243,18 @@ check_proxy_list() {
return 0
}
# --- 1. bootstrap resolver -------------------------------------------------
# --- 1. bootstrap resolver, derived from a bare-IP China DNS server ---------
dir="$WORK/bootstrap"
if stage_case "$dir" "bootstrap" "$BOOTSTRAP_DNS" "$FIXTURE"; then
if stage_case "$dir" "bootstrap" "$CHINA_DNS_IP" "$FIXTURE"; then
json="$dir/run/sing-box-c.json"
bootstrap_line="$(grep -nF '"tag": "bootstrap-dns"' "$json" | head -1 | cut -d: -f1)"
if [ -z "$bootstrap_line" ]; then
echo "FAIL: bootstrap: no bootstrap-dns server was emitted"
echo "FAIL: bootstrap: a bare-IP China DNS server produced no bootstrap-dns server"
FAILED=1
elif ! sed -n "$bootstrap_line,$((bootstrap_line + 4))p" "$json" | grep -qF "\"$BOOTSTRAP_DNS\""; then
echo "FAIL: bootstrap: bootstrap-dns does not carry $BOOTSTRAP_DNS"
elif ! sed -n "$bootstrap_line,$((bootstrap_line + 4))p" "$json" | grep -qF "\"$CHINA_DNS_IP\""; then
echo "FAIL: bootstrap: bootstrap-dns does not carry the China DNS server ($CHINA_DNS_IP)"
FAILED=1
elif sed -n "$bootstrap_line,$((bootstrap_line + 4))p" "$json" | grep -qF '"domain_resolver"'; then
echo "FAIL: bootstrap: the bootstrap resolver has a domain_resolver of its own"
@@ -264,26 +270,30 @@ if stage_case "$dir" "bootstrap" "$BOOTSTRAP_DNS" "$FIXTURE"; then
echo "FAIL: bootstrap: main-dns still resolves through the WAN resolver"
FAILED=1
else
echo "PASS: bootstrap: main-dns resolves its own hostname through bootstrap-dns ($BOOTSTRAP_DNS)"
echo "PASS: bootstrap: main-dns resolves its own hostname through bootstrap-dns ($CHINA_DNS_IP)"
fi
fi
else
FAILED=1
fi
# --- 2. the default (no bootstrap configured) ------------------------------
# --- 2. a hostname China DNS server is not a usable bootstrap --------------
# The option also accepts a DoH/DoT URL, and a URL is a hostname: using one to
# resolve the hostname it would be needed to resolve is the cycle this whole
# mechanism exists to avoid. It must fall back to the WAN resolver, loudly
# enough to be visible here, rather than to a broken bootstrap.
dir="$WORK/default"
if stage_case "$dir" "default" "" "$FIXTURE"; then
dir="$WORK/hostname"
if stage_case "$dir" "hostname" "$CHINA_DNS_DOH" "$FIXTURE"; then
json="$dir/run/sing-box-c.json"
if grep -qF '"tag": "bootstrap-dns"' "$json"; then
echo "FAIL: default: an unconfigured bootstrap resolver was emitted"
echo "FAIL: hostname: a DoH China DNS server was used as the bootstrap resolver"
FAILED=1
elif ! grep -qF '"server": "default-dns"' "$json"; then
echo "FAIL: default: main-dns no longer resolves through the WAN resolver"
echo "FAIL: hostname: no bootstrap server and main-dns does not use default-dns either"
FAILED=1
else
echo "PASS: default: no bootstrap server, main-dns still resolves through default-dns"
echo "PASS: hostname: a DoH China DNS server yields no bootstrap server, main-dns uses default-dns"
fi
else
FAILED=1