💐 Sync 2026-10-01 01:11:54

This commit is contained in:
github-actions[bot] committed 2026-10-01 01:11:54 +08:00
1 parent 8f2ec5c4da
commit 2909cae209
44 files changed
+1092 -2287

No files matched your search

@@ -1,51 +0,0 @@
# 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'
@@ -1,33 +0,0 @@
# 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'
@@ -1,39 +0,0 @@
# 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'
@@ -1,29 +0,0 @@
# 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'
@@ -1,32 +0,0 @@
# 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'
@@ -1,38 +0,0 @@
# 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'
@@ -1,51 +0,0 @@
# 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'
@@ -1,38 +0,0 @@
# 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'
@@ -109,8 +109,6 @@ 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
@@ -303,8 +301,6 @@ 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
@@ -389,8 +385,6 @@ 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
@@ -532,8 +526,6 @@ 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
@@ -737,8 +729,6 @@ 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
@@ -837,8 +827,6 @@ 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
@@ -981,8 +969,6 @@ 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
@@ -1134,8 +1120,6 @@ 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
@@ -1220,8 +1204,6 @@ 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
@@ -1429,8 +1411,6 @@ 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
@@ -1496,8 +1476,6 @@ 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
@@ -1645,8 +1623,6 @@ 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
@@ -1829,8 +1805,6 @@ 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
@@ -1952,8 +1926,6 @@ 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
@@ -133,35 +133,5 @@ 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);
@@ -378,29 +378,5 @@ 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
@@ -242,22 +242,6 @@ 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
+212 -188
View File
@@ -40,6 +40,10 @@
"ruleset",
"Rule Set"
],
[
"dns_cache",
"DNS Settings"
],
[
"control",
"Access Control"
@@ -271,7 +275,7 @@
],
"subsection": null,
"props": {
"validate": "fn(349:31d7a7fd11ff)"
"validate": "fn(376:3a1eaf5f61c2)"
}
},
{
@@ -494,7 +498,7 @@
"subsection": null,
"props": {
"default": "nil",
"load": "fn(347:1e6b4e82357b)",
"load": "fn(347:bdff551797c2)",
"rmempty": false
}
},
@@ -508,7 +512,7 @@
"subsection": null,
"props": {
"default": "default-dns",
"load": "fn(326:b9d3cd52289f)",
"load": "fn(326:345de0db50cf)",
"rmempty": false
}
}
@@ -593,7 +597,7 @@
"values": [],
"subsection": null,
"props": {
"load": "fn(356:8d5ad606a86f)",
"load": "fn(356:d9c0755071ec)",
"modalonly": true
}
},
@@ -669,7 +673,7 @@
"subsection": null,
"props": {
"editable": true,
"load": "fn(284:01fc7f231969)",
"load": "fn(284:530aca6575b5)",
"validate": "fn(487:34bee8692641)"
}
},
@@ -1024,7 +1028,7 @@
"values": [],
"subsection": null,
"props": {
"load": "fn(219:0275f2977497)",
"load": "fn(219:b7b6c816012f)",
"modalonly": true
}
},
@@ -1098,7 +1102,7 @@
"subsection": null,
"props": {
"editable": true,
"load": "fn(263:a19a1b2bf7ba)",
"load": "fn(263:6f52e51cea77)",
"rmempty": false
}
},
@@ -1111,10 +1115,6 @@
[
"action",
"route"
],
[
"action",
"route-options"
]
],
"values": [],
@@ -1133,10 +1133,6 @@
[
"action",
"route"
],
[
"action",
"route-options"
]
],
"values": [],
@@ -1155,10 +1151,6 @@
[
"action",
"route"
],
[
"action",
"route-options"
]
],
"values": [],
@@ -1176,10 +1168,6 @@
[
"action",
"route"
],
[
"action",
"route-options"
]
],
"values": [],
@@ -1197,10 +1185,6 @@
[
"action",
"route"
],
[
"action",
"route-options"
]
],
"values": [],
@@ -1219,15 +1203,7 @@
[
"action",
"route"
],
[
"action",
"route-options"
],
{
"tls_fragment": "1",
"!reverse": true
}
]
],
"values": [],
"subsection": null,
@@ -1244,15 +1220,7 @@
[
"action",
"route"
],
[
"action",
"route-options"
],
{
"tls_record_fragment": "1",
"!reverse": true
}
]
],
"values": [],
"subsection": null,
@@ -1288,10 +1256,6 @@
[
"action",
"route"
],
[
"action",
"route-options"
]
],
"values": [],
@@ -1311,10 +1275,6 @@
"action",
"route"
],
[
"action",
"route-options"
],
[
"tls_spoof",
{}
@@ -1361,7 +1321,7 @@
"values": [],
"subsection": null,
"props": {
"load": "fn(356:8d5ad606a86f)",
"load": "fn(356:d9c0755071ec)",
"modalonly": true
}
},
@@ -1580,8 +1540,7 @@
"values": [],
"subsection": null,
"props": {
"modalonly": true,
"validate": "fn(184:0290625d9d6f)"
"modalonly": true
}
},
{
@@ -1744,8 +1703,7 @@
"values": [],
"subsection": null,
"props": {
"modalonly": true,
"validate": "fn(184:0290625d9d6f)"
"modalonly": true
}
}
],
@@ -1753,100 +1711,16 @@
},
"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. 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",
"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": [],
"depends": [
[
"routing_mode",
"custom"
]
],
"values": [],
"subsection": {
"id": "GridSection:_dns#5",
@@ -1859,12 +1733,7 @@
"name": "default_strategy",
"title": "Default DNS strategy",
"description": "The DNS strategy for resolving the domain name in the address.",
"depends": [
[
"routing_mode",
"custom"
]
],
"depends": [],
"values": [
[
"",
@@ -1895,17 +1764,12 @@
"name": "default_server",
"title": "Default DNS server",
"description": null,
"depends": [
[
"routing_mode",
"custom"
]
],
"depends": [],
"values": [],
"subsection": null,
"props": {
"default": "default-dns",
"load": "fn(326:b9d3cd52289f)",
"load": "fn(326:345de0db50cf)",
"rmempty": false
}
},
@@ -1939,12 +1803,7 @@
"name": "client_subnet",
"title": "EDNS Client subnet",
"description": "Append a <code>edns0-subnet</code> OPT extra record with the specified IP prefix to every query by default.<br/>If value is an IP address instead of prefix, <code>/32</code> or <code>/128</code> will be appended automatically.",
"depends": [
[
"routing_mode",
"custom"
]
],
"depends": [],
"values": [],
"subsection": null,
"props": {
@@ -2150,7 +2009,7 @@
"kind": "DynamicList",
"name": "headers",
"title": "Headers",
"description": "Additional headers to be sent to the DNS server, one <code>Name: Value</code> per line.",
"description": "Additional headers to be sent to the DNS server.",
"depends": [
[
"type",
@@ -2205,7 +2064,7 @@
"values": [],
"subsection": null,
"props": {
"load": "fn(384:2a0361acfbde)",
"load": "fn(384:9fa59da460cf)",
"modalonly": true,
"validate": "fn(322:e76c15d647fc)"
}
@@ -2259,7 +2118,7 @@
"props": {
"default": "direct-out",
"editable": true,
"load": "fn(263:a19a1b2bf7ba)",
"load": "fn(263:6f52e51cea77)",
"rmempty": false
}
}
@@ -2383,8 +2242,7 @@
"values": [],
"subsection": null,
"props": {
"modalonly": true,
"validate": "fn(286:47dea50f33ea)"
"modalonly": true
}
},
{
@@ -2474,7 +2332,7 @@
"values": [],
"subsection": null,
"props": {
"load": "fn(219:0275f2977497)",
"load": "fn(219:b7b6c816012f)",
"modalonly": true
}
},
@@ -2552,7 +2410,7 @@
"subsection": null,
"props": {
"editable": true,
"load": "fn(326:b9d3cd52289f)",
"load": "fn(326:345de0db50cf)",
"rmempty": false
}
},
@@ -3022,8 +2880,7 @@
"values": [],
"subsection": null,
"props": {
"modalonly": true,
"validate": "fn(184:0290625d9d6f)"
"modalonly": true
}
},
{
@@ -3211,8 +3068,7 @@
"values": [],
"subsection": null,
"props": {
"modalonly": true,
"validate": "fn(184:0290625d9d6f)"
"modalonly": true
}
}
],
@@ -3290,13 +3146,9 @@
"kind": "ListValue",
"name": "format",
"title": "Format",
"description": "Leave empty to infer it from a <code>.json</code> or <code>.srs</code> path/url; sing-box requires it for any other extension.",
"description": null,
"depends": [],
"values": [
[
"",
"Automatic (by extension)"
],
[
"binary",
"Binary file"
@@ -3308,7 +3160,8 @@
],
"subsection": null,
"props": {
"rmempty": true
"default": "binary",
"rmempty": false
}
},
{
@@ -3364,7 +3217,7 @@
"values": [],
"subsection": null,
"props": {
"load": "fn(293:81de93c500ec)"
"load": "fn(293:8a1c8158e8ba)"
}
},
{
@@ -3425,6 +3278,177 @@
},
"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",
@@ -3432,7 +3456,7 @@
"depends": [],
"values": [],
"subsection": {
"id": "GridSection:_control#9",
"id": "GridSection:_control#10",
"kind": "GridSection",
"name": "_control",
"tabs": [
@@ -3488,12 +3512,12 @@
"kind": "ListValue",
"name": "lan_proxy_mode",
"title": "Proxy filter mode",
"description": "\"No filter\" proxies every LAN client, which is the default; the other two values select clients by the lists below.",
"description": null,
"depends": [],
"values": [
[
"disabled",
"No filter (proxy every client)"
"Disable"
],
[
"listed_only",
@@ -96,28 +96,6 @@ 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
@@ -203,14 +181,6 @@ 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"
@@ -975,7 +975,7 @@ else
esac
fi
# 3) P2 #4 / §2.2.1: the NAPTR (qtype 35) bypass suffixes must be
# 3) P2 #4 / §2.2.1: the three 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,6 +983,7 @@ fi
# 35
# ],
# "domain_suffix": [
# "r.10086.cn",
# "10086.cn",
# "pub.3gppnetwork.org"
# ],
@@ -999,7 +1000,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 '10086.cn' 'pub.3gppnetwork.org'; do
for sfx in 'r.10086.cn' '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:"
@@ -1008,7 +1009,7 @@ else
fi
done
if [ "$naptr_ok" = 1 ]; then
echo "PASS: naptr-bypass-domains: qtype-35 rule domain_suffix carries every NAPTR_BYPASS_SUFFIXES entry"
echo "PASS: naptr-bypass-domains: qtype-35 rule domain_suffix carries the 3 NAPTR_BYPASS_SUFFIXES entries"
fi
fi
@@ -1083,220 +1084,4 @@ 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.
# --- 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}
exit ${FAILED:-0}
@@ -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',
'gfw_set_counts', 'log_clean', 'node_parse', 'resources_get_version',
'resources_update', 'singbox_generator', 'singbox_get_features',
'update_subscriptions', 'update_subscriptions_status'
'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) {