Files
NeoTachyon/tests/runtime_state_predicates.sh
T

1598 lines
50 KiB
Bash

#!/usr/bin/env bash
set -eo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TACHYON_LIB="$ROOT_DIR/tachyon/files/usr/lib"
STATE_UC="$ROOT_DIR/tachyon/files/usr/lib/service/state.uc"
NFT_UC="$ROOT_DIR/tachyon/files/usr/lib/nft/apply.uc"
UCODE_BIN="$(command -v ucode)"
WORK_DIR="$(mktemp -d)"
export ZAPRET_DEFAULT_NFQWS_OPT="--default-zapret"
export ZAPRET2_DEFAULT_NFQWS2_OPT="--default-zapret2"
export BYEDPI_DEFAULT_CMD_OPTS="--default-bye"
export TACHYON_FAKE_INIT_CAPTURE="$WORK_DIR/pending-reload-init.args"
cleanup() {
rm -rf "$WORK_DIR"
}
trap cleanup EXIT
fail() {
printf 'FAIL: %s\n' "$1" >&2
exit 1
}
assert_eq() {
local expected="$1"
local actual="$2"
local label="$3"
[ "$actual" = "$expected" ] || fail "$label: expected '$expected', got '$actual'"
}
md5_file() {
md5sum "$1" | awk '{print $1}'
}
state_ucode() {
ucode -L "$TACHYON_LIB" "$STATE_UC" "$@"
}
nft_ucode() {
ucode -L "$TACHYON_LIB" "$NFT_UC" "$@"
}
state_ucode list-has-remote-references "local.lst https://example.com/list.txt" >/dev/null ||
fail "remote reference should be detected"
if state_ucode list-has-remote-references "local.lst /tmp/list.txt" >/dev/null 2>&1; then
fail "local-only references should not be remote"
fi
state_ucode list-has-remote-sing-box-rulesets "https://example.com/rules.srs" >/dev/null ||
fail "remote sing-box ruleset should be detected"
if state_ucode list-has-remote-sing-box-rulesets "/tmp/rules.srs" >/dev/null 2>&1; then
fail "local sing-box ruleset should not be remote"
fi
assert_eq "meta telegram discord" \
"$(state_ucode filter-community-subnet-lists "geoblock meta telegram youtube discord")" \
"community subnet list filter"
state_ucode community-service-has-subnet-list roblox >/dev/null ||
fail "roblox should have subnet list"
if state_ucode community-service-has-subnet-list youtube >/dev/null 2>&1; then
fail "youtube should not be a subnet-list community service"
fi
state_ucode rule-has-list-update-source 1 proxy "youtube telegram" "" "" "" "" >/dev/null ||
fail "community subnet service should require list update"
state_ucode rule-has-list-update-source 1 proxy "" "https://example.com/domains.lst" "" "" "" >/dev/null ||
fail "remote domain list should require list update"
state_ucode rule-has-list-update-source 1 proxy "" "" "" "" "local.lst https://example.com/mixed.lst" >/dev/null ||
fail "remote domain_ip list should require list update"
if state_ucode rule-has-list-update-source 0 proxy "telegram" "https://example.com/domains.lst" "" "" "" >/dev/null 2>&1; then
fail "disabled rule should not require list update"
fi
state_ucode rule-has-list-update-source 1 dns "" "" "" "" "https://example.com/domains.lst" >/dev/null ||
fail "remote DNS domain list should require list update"
if state_ucode rule-has-list-update-source 1 dns "telegram" "" "" "" "" >/dev/null 2>&1; then
fail "DNS rules should not import built-in subnet lists"
fi
state_ucode rule-has-nft-list-update-source 1 proxy "telegram" "" "" "" >/dev/null ||
fail "community subnet service should require nft list update"
state_ucode rule-has-nft-list-update-source 1 proxy "" "https://example.com/subnets.lst" "" "" >/dev/null ||
fail "remote subnet list should require nft list update"
if state_ucode rule-has-nft-list-update-source 1 proxy "" "" "" "local.lst" >/dev/null 2>&1; then
fail "local-only domain_ip list should not require nft list update"
fi
if state_ucode rule-has-nft-list-update-source 1 dns "telegram" "https://example.com/subnets.lst" "" "https://example.com/mixed.lst" >/dev/null 2>&1; then
fail "DNS rules should never require nft list updates"
fi
state_ucode rule-has-subscription-update-source 1 "1h" >/dev/null ||
fail "subscription proxy with update interval should require subscription update"
if state_ucode rule-has-subscription-update-source 1 "" >/dev/null 2>&1; then
fail "empty subscription update interval should not require subscription update"
fi
if state_ucode rule-has-subscription-update-source 0 "1h" >/dev/null 2>&1; then
fail "non-subscription proxy should not require subscription update"
fi
state_ucode time-sync-needed 2023 >/dev/null ||
fail "time sync should be needed for invalid pre-2024 clock"
if state_ucode time-sync-needed 2024 >/dev/null 2>&1; then
fail "time sync should not be needed for current clock"
fi
if state_ucode sing-box-pid-replaced-fixture 3285 3285 1 >/dev/null 2>&1; then
fail "sing-box reload must not accept the old stable PID"
fi
if state_ucode sing-box-pid-replaced-fixture 3285 0 0 >/dev/null 2>&1; then
fail "sing-box reload must not accept a missing replacement process"
fi
state_ucode sing-box-pid-replaced-fixture 3285 4127 1 >/dev/null ||
fail "sing-box reload should accept a different live sing-box PID"
if state_ucode sing-box-pid-replaced-fixture 3285 4127 0 >/dev/null 2>&1; then
fail "sing-box reload must reject a replacement PID owned by another executable"
fi
assert_eq "3285" \
"$(state_ucode sing-box-reload-previous-pid-fixture 3285 old new)" \
"changed sing-box config replacement PID"
assert_eq "0" \
"$(state_ucode sing-box-reload-previous-pid-fixture 3285 same same)" \
"unchanged sing-box config replacement PID"
assert_eq "0" \
"$(state_ucode sing-box-reload-previous-pid-fixture missing old new)" \
"missing sing-box PID replacement constraint"
if state_ucode sing-box-runtime-reload-needed-fixture same same 0 >/dev/null 2>&1; then
fail "unchanged sing-box config must skip an ordinary runtime reload"
fi
state_ucode sing-box-runtime-reload-needed-fixture old new 0 >/dev/null ||
fail "changed sing-box config must reload the runtime"
state_ucode sing-box-runtime-reload-needed-fixture same same 1 >/dev/null ||
fail "forced runtime reload must reload unchanged sing-box config"
assert_eq "8" \
"$(state_ucode process-age-seconds-fixture 786161359 786162159)" \
"process age on a shared kernel tick scale"
if state_ucode process-age-seconds-fixture 786162159 786161359 >/dev/null 2>&1; then
fail "process age must reject a current tick value older than the process"
fi
if sed -n '/^function process_age_seconds(pid) {$/,/^}$/p' "$STATE_UC" | grep -Fq '/proc/uptime'; then
fail "process age must not mix process start ticks with virtualized /proc/uptime"
fi
mkdir -p "$WORK_DIR/stable-start-bin"
cp "$(command -v ucode)" "$WORK_DIR/stable-start-bin/sing-box"
cat >"$WORK_DIR/stable-start-bin/ubus" <<'SH'
#!/usr/bin/env bash
set -eo pipefail
pid="$(cat "${SING_BOX_TEST_PID_FILE:?}")"
if [ -r "/proc/$pid/exe" ] && [ "$(basename "$(readlink "/proc/$pid/exe")")" = "sing-box" ]; then
printf '{"sing-box":{"instances":{"instance1":{"running":true,"pid":%s}}}}\n' "$pid"
else
printf '{"sing-box":{"instances":{}}}\n'
fi
SH
cat >"$WORK_DIR/stable-start-bin/nft" <<'SH'
#!/usr/bin/env bash
exit 0
SH
cat >"$WORK_DIR/stable-start-bin/ucode" <<'SH'
#!/usr/bin/env bash
if [ "$#" -ge 4 ] && [ "$1" = "-L" ] && [ "${3##*/}" = "apply.uc" ] && [ "$4" = "tproxy-route-rule-present" ]; then
exit 0
fi
exec "${REAL_UCODE:?}" "$@"
SH
chmod 0755 "$WORK_DIR/stable-start-bin/ubus" "$WORK_DIR/stable-start-bin/nft" "$WORK_DIR/stable-start-bin/ucode"
"$WORK_DIR/stable-start-bin/sing-box" -e "system('sleep 30')" &
sing_box_pid=$!
printf '%s\n' "$sing_box_pid" >"$WORK_DIR/sing-box.pid"
if ! PATH="$WORK_DIR/stable-start-bin:$PATH" \
REAL_UCODE="$UCODE_BIN" \
SING_BOX_TEST_PID_FILE="$WORK_DIR/sing-box.pid" \
state_ucode sing-box-service-running; then
kill "$sing_box_pid" >/dev/null 2>&1 || true
wait "$sing_box_pid" 2>/dev/null || true
fail "stable-start fixture must expose a running sing-box process"
fi
if ! PATH="$WORK_DIR/stable-start-bin:$PATH" \
REAL_UCODE="$UCODE_BIN" \
SING_BOX_TEST_PID_FILE="$WORK_DIR/sing-box.pid" \
state_ucode tachyon-running tachyon TachyonTable 0x00100000; then
kill "$sing_box_pid" >/dev/null 2>&1 || true
wait "$sing_box_pid" 2>/dev/null || true
fail "stable-start fixture must expose configured Tachyon networking"
fi
if ! PATH="$WORK_DIR/stable-start-bin:$PATH" \
REAL_UCODE="$UCODE_BIN" \
SING_BOX_TEST_PID_FILE="$WORK_DIR/sing-box.pid" \
state_ucode wait-tachyon-stable-start tachyon TachyonTable 0x00100000 2 2; then
kill "$sing_box_pid" >/dev/null 2>&1 || true
wait "$sing_box_pid" 2>/dev/null || true
fail "stable-start wait must check runtime state after its final sleep"
fi
kill "$sing_box_pid" >/dev/null 2>&1 || true
wait "$sing_box_pid" 2>/dev/null || true
PENDING_RELOAD_FILE="$WORK_DIR/reload.pending"
state_ucode mark-pending-reload "$PENDING_RELOAD_FILE" "reload_busy"
grep -Fxq "reason=reload_busy" "$PENDING_RELOAD_FILE" ||
fail "pending reload reason should be written by ucode"
updated_at="$(sed -n 's/^updated_at=//p' "$PENDING_RELOAD_FILE")"
case "$updated_at" in
''|*[!0-9]*) fail "pending reload updated_at should be numeric, got '$updated_at'" ;;
esac
state_ucode consume-pending-reload "$PENDING_RELOAD_FILE" >/dev/null ||
fail "pending reload should be consumed"
[ ! -e "$PENDING_RELOAD_FILE" ] ||
fail "pending reload file should be removed after consume"
if state_ucode consume-pending-reload "$PENDING_RELOAD_FILE" >/dev/null 2>&1; then
fail "missing pending reload should not be consumed"
fi
cat >"$WORK_DIR/fake-init" <<'SH'
#!/bin/sh
printf '%s\n' "$1" >"$TACHYON_FAKE_INIT_CAPTURE"
SH
chmod +x "$WORK_DIR/fake-init"
state_ucode mark-pending-reload "$PENDING_RELOAD_FILE" "reload_busy"
state_ucode run-pending-reload-if-requested "$PENDING_RELOAD_FILE" "$WORK_DIR/fake-init"
for _ in $(seq 1 20); do
[ -s "$TACHYON_FAKE_INIT_CAPTURE" ] && break
sleep 1
done
assert_eq "reload" \
"$(cat "$TACHYON_FAKE_INIT_CAPTURE")" \
"pending reload should invoke init.d reload"
[ ! -e "$PENDING_RELOAD_FILE" ] ||
fail "pending reload should be consumed when worker is started"
LOCK_DIR="$WORK_DIR/runtime.lock"
state_ucode acquire-runtime-dir-lock "$LOCK_DIR" "$$" ||
fail "ucode should acquire runtime dir lock"
assert_eq "$$" "$(cat "$LOCK_DIR/pid")" "runtime lock owner pid"
if state_ucode acquire-runtime-dir-lock "$LOCK_DIR" "$$" >/dev/null 2>&1; then
fail "ucode should reject a lock held by a live pid"
fi
if state_ucode acquire-runtime-dir-lock-wait "$LOCK_DIR" "$$" 0 >/dev/null 2>&1; then
fail "ucode wait lock should time out for a live holder"
fi
state_ucode release-runtime-dir-lock "$LOCK_DIR"
[ ! -e "$LOCK_DIR" ] ||
fail "ucode should remove runtime dir lock"
mkdir -p "$LOCK_DIR"
printf '%s\n' 999999 >"$LOCK_DIR/pid"
state_ucode acquire-runtime-dir-lock "$LOCK_DIR" "$$" ||
fail "ucode should replace a stale runtime dir lock"
assert_eq "$$" "$(cat "$LOCK_DIR/pid")" "stale runtime lock owner pid"
state_ucode release-runtime-dir-lock "$LOCK_DIR"
SNAPSHOT_FILE="$WORK_DIR/reload-state.snapshot"
TARGET_RELOAD_STATE="$WORK_DIR/reload-state.target"
CACHE_DIR="$WORK_DIR/rule-cache"
mkdir -p "$CACHE_DIR"
printf 'format=1\nservice_trigger_signature=abc\n' >"$SNAPSHOT_FILE"
state_ucode write-captured-reload-state "$TARGET_RELOAD_STATE" "$SNAPSHOT_FILE" 1 "$CACHE_DIR" 1 1
assert_eq "format=1
service_trigger_signature=abc" "$(cat "$TARGET_RELOAD_STATE")" "captured reload state copy"
[ ! -e "$SNAPSHOT_FILE" ] ||
fail "ucode should clear captured reload snapshot after final write"
[ ! -e "$CACHE_DIR" ] ||
fail "ucode should clean rule condition cache after final reload state write"
printf 'format=1\n' >"$SNAPSHOT_FILE"
printf 'old=1\n' >"$TARGET_RELOAD_STATE"
printf 'stale=1\n' >"$TARGET_RELOAD_STATE.snapshot.100.200"
printf 'stale=1\n' >"$TARGET_RELOAD_STATE.snapshot.interrupted"
printf 'keep=1\n' >"$WORK_DIR/unrelated.snapshot.100.200"
state_ucode clear-reload-state "$TARGET_RELOAD_STATE" "$SNAPSHOT_FILE"
[ ! -e "$TARGET_RELOAD_STATE" ] ||
fail "ucode should clear reload state file"
[ ! -e "$SNAPSHOT_FILE" ] ||
fail "ucode should clear reload state snapshot file"
[ ! -e "$TARGET_RELOAD_STATE.snapshot.100.200" ] &&
[ ! -e "$TARGET_RELOAD_STATE.snapshot.interrupted" ] ||
fail "ucode should clear stale snapshots owned by the reload state file"
[ -e "$WORK_DIR/unrelated.snapshot.100.200" ] ||
fail "ucode should preserve unrelated snapshot files"
cat >"$WORK_DIR/service-dns-state.json" <<'JSON'
{
"settings": {
"enable_badwan_interface_monitoring": "1",
"badwan_monitored_interfaces": [ "wan", "wwan" ],
"badwan_reload_delay": "3500",
"dont_touch_dhcp": "1"
},
"dhcp_dnsmasq": {
"server": [ "1.1.1.1#53", "8.8.8.8" ],
"noresolv": "1",
"cachesize": "0",
"tachyon_server": [ "127.0.0.42#53" ],
"tachyon_noresolv": "0",
"tachyon_cachesize": "1500"
},
"legacy_dnsmasq_present": true
}
JSON
cat >"$WORK_DIR/service-trigger.expected" <<'EOF_SERVICE_TRIGGER'
[settings.enable_badwan_interface_monitoring]
1
[settings.badwan_monitored_interfaces]
wan wwan
[settings.badwan_reload_delay]
3500
EOF_SERVICE_TRIGGER
cat >"$WORK_DIR/dnsmasq-signature.expected" <<'EOF_DNSMASQ_SIG'
[settings.dont_touch_dhcp]
1
[dhcp.@dnsmasq[0].server]
1.1.1.1#53 8.8.8.8
[dhcp.@dnsmasq[0].noresolv]
1
[dhcp.@dnsmasq[0].cachesize]
0
[dhcp.@dnsmasq[0].tachyon_server]
127.0.0.42#53
[dhcp.@dnsmasq[0].tachyon_noresolv]
0
[dhcp.@dnsmasq[0].tachyon_cachesize]
1500
[dhcp.tachyon.present]
1
EOF_DNSMASQ_SIG
assert_eq "$(md5_file "$WORK_DIR/service-trigger.expected")" \
"$(state_ucode service-trigger-signature-fixture "$WORK_DIR/service-dns-state.json")" \
"service trigger signature hash"
assert_eq "$(md5_file "$WORK_DIR/dnsmasq-signature.expected")" \
"$(state_ucode dnsmasq-signature-fixture "$WORK_DIR/service-dns-state.json")" \
"dnsmasq signature hash"
assert_eq "1" \
"$(state_ucode dont-touch-dhcp-fixture "$WORK_DIR/service-dns-state.json")" \
"dont_touch_dhcp value"
cat >"$WORK_DIR/service-trigger-disabled.json" <<'JSON'
{
"settings": {
"enable_badwan_interface_monitoring": "0",
"badwan_monitored_interfaces": [ "wan" ],
"badwan_reload_delay": "3500"
}
}
JSON
cat >"$WORK_DIR/service-trigger-disabled.expected" <<'EOF_SERVICE_TRIGGER_DISABLED'
[settings.enable_badwan_interface_monitoring]
0
EOF_SERVICE_TRIGGER_DISABLED
assert_eq "$(md5_file "$WORK_DIR/service-trigger-disabled.expected")" \
"$(state_ucode service-trigger-signature-fixture "$WORK_DIR/service-trigger-disabled.json")" \
"disabled service trigger signature hash"
assert_eq "0" \
"$(state_ucode dont-touch-dhcp-fixture "$WORK_DIR/service-trigger-disabled.json")" \
"default dont_touch_dhcp value"
cat >"$WORK_DIR/service-trigger-default-delay.json" <<'JSON'
{
"settings": {
"enable_badwan_interface_monitoring": "1",
"badwan_monitored_interfaces": [ "wan" ]
}
}
JSON
cat >"$WORK_DIR/service-trigger-default-delay.expected" <<'EOF_SERVICE_TRIGGER_DEFAULT_DELAY'
[settings.enable_badwan_interface_monitoring]
1
[settings.badwan_monitored_interfaces]
wan
[settings.badwan_reload_delay]
2000
EOF_SERVICE_TRIGGER_DEFAULT_DELAY
assert_eq "$(md5_file "$WORK_DIR/service-trigger-default-delay.expected")" \
"$(state_ucode service-trigger-signature-fixture "$WORK_DIR/service-trigger-default-delay.json")" \
"default badwan reload delay signature hash"
cat >"$WORK_DIR/sing-box-signature.json" <<'JSON'
{
"settings": {
"dns_type": "tcp",
"dns_strategy": "prefer_ipv6",
"dns_server": "9.9.9.9",
"bootstrap_dns_server": "1.0.0.1",
"dns_rewrite_ttl": "120",
"output_network_interface": "wan",
"disable_quic": "on",
"list_update_enabled": "1",
"update_interval": "2h",
"cache_path": "/tmp/cache.db",
"config_path": "/etc/sing-box/custom.json",
"log_level": "debug",
"service_listen_address": "127.0.0.1",
"enable_yacd": "yes",
"enable_yacd_wan_access": "1",
"yacd_secret_key": "secret",
"download_lists_via_proxy": "0",
"download_subscriptions_via_proxy": "on",
"download_components_via_proxy": "0",
"download_lists_via_proxy_section": "proxy1"
},
"runtime": {
"mwan3_active": "1"
},
"section": [
{
".name": "disabled_proxy",
".type": "section",
"enabled": "0",
"action": "proxy",
"selector_proxy_links": [ "ignored" ]
},
{
".name": "proxy1",
".type": "section",
"enabled": "1",
"action": "proxy",
"selector_proxy_links": [ "vless://one", "vless://two" ],
"dashboard_filter_mode": "mixed",
"dashboard_detect_server_country": "country_is",
"dashboard_include_countries": [ "NL" ],
"dashboard_include_outbounds": [ "node-a" ],
"dashboard_include_regex": [ "^A" ],
"dashboard_include_proxy_parameters": "1",
"dashboard_include_protocols": [ "vless" ],
"dashboard_include_transports": [ "ws" ],
"dashboard_include_securities": [ "reality" ],
"dashboard_include_groups": [ "urltest" ],
"dashboard_exclude_countries": [ "RU" ],
"dashboard_exclude_outbounds": [ "node-b" ],
"dashboard_exclude_regex": [ "backup" ],
"dashboard_exclude_proxy_parameters": "1",
"dashboard_exclude_protocols": [ "http" ],
"dashboard_exclude_transports": [ "tcp" ],
"dashboard_exclude_securities": [ "none" ],
"dashboard_exclude_groups": [ "urltest" ],
"subscription_urls": [ "https://example.com/sub.txt" ],
"subscription_url_settings": "{\"https://example.com/sub.txt\":{\"prefix_nodes\":\"1\",\"node_prefix\":\"Example\"}}",
"urltest_enabled": "1",
"detect_server_country": "1",
"urltest_tolerance": "75",
"urltest_filter_mode": "exclude",
"urltest_exclude_countries": [ "RU" ],
"urltest_include_outbounds": [ "node-a" ],
"subscription_update_enabled": "1",
"outbound_detour_enabled": "1",
"outbound_detour_section": "out1",
"mixed_proxy_enabled": "1",
"mixed_proxy_port": "2080",
"mixed_proxy_auth_enabled": "1",
"mixed_proxy_username": "user",
"mixed_proxy_password": "pass",
"resolve_real_ip_for_routing": "1",
"domain": [ "legacy.example" ],
"domain_suffix": [ "full:full.example", "suffix.example" ],
"domain_keyword": [ "ads" ],
"domain_regex": [ "^foo" ],
"ip_cidr": [ "203.0.113.1", "bad" ],
"source_ip_cidr": [ "198.51.100.0/24" ],
"ports": [ "443" ],
"ports_text": "80,443-444",
"fully_routed_ips": [ "198.51.100.2" ],
"community_lists": [ "telegram" ],
"rule_set": [ "https://example.com/remote.srs" ],
"rule_set_with_subnets": [ "local.srs" ],
"domain_ip_lists": [ "local.lst" ]
},
{
".name": "out1",
".type": "section",
"enabled": "1",
"action": "outbound",
"outbound_json": "{\"type\":\"direct\"}",
"outbound_detour_enabled": "0",
"mixed_proxy_enabled": "0",
"resolve_real_ip_for_routing": "0"
},
{
".name": "bye1",
".type": "section",
"enabled": "1",
"action": "byedpi",
"mixed_proxy_enabled": "0"
},
{
".name": "vpn1",
".type": "section",
"enabled": "1",
"action": "vpn",
"interface": "wg0",
"mixed_proxy_enabled": "0",
"resolve_real_ip_for_routing": "yes"
}
],
"section_interface": [
{
".name": "vpn-interface",
".type": "section_interface",
"section": "vpn1",
"name": "wg0",
"domain_resolver_enabled": "1",
"domain_resolver_dns_type": "doh",
"domain_resolver_dns_server": "https://dns.example/dns-query"
}
],
"server": [
{
".name": "srv_disabled",
".type": "server",
"enabled": "0",
"label": "ignored"
},
{
".name": "srv1",
".type": "server",
"enabled": "1",
"listen_port": "8443",
"server_users": [ "alice", "bob" ],
"tls_server_name": "example.com",
"transport": "ws",
"transport_path": "/ws",
"tailscale_accept_routes": "1"
}
]
}
JSON
cat >"$WORK_DIR/sing-box-signature.expected" <<'EOF_SING_BOX_SIG'
[settings.dns_type]
tcp
[settings.dns_strategy]
prefer_ipv6
[settings.dns_upstream_mode]
sequential
[settings.dns_server]
9.9.9.9
[settings.bootstrap_dns_server]
1.0.0.1
[settings.dns_check_interval]
10s
[settings.dns_recovery_check_interval]
60s
[settings.dns_check_timeout]
2s
[settings.dns_failure_threshold]
3
[settings.dns_recovery_threshold]
3
[settings.dns_detour_enabled]
0
[settings.dns_rewrite_ttl]
120
[settings.output_network_interface]
wan
[settings.disable_quic]
1
[settings.update_interval]
2h
[settings.cache_path]
/tmp/cache.db
[settings.config_path]
/etc/sing-box/custom.json
[settings.log_level]
debug
[settings.service_listen_address]
127.0.0.1
[runtime.mwan3_active]
1
[settings.enable_yacd]
1
[settings.enable_yacd_wan_access]
1
[settings.yacd_secret_key]
secret
[settings.download_lists_via_proxy]
0
[settings.download_components_via_proxy]
0
[rule.proxy1.action]
proxy
[rule.proxy1.connection_urls]
[ "vless://one", "vless://two" ]
[rule.proxy1.subscription_urls]
[ { "url": "https://example.com/sub.txt", "subscription_update_enabled": "1", "subscription_update_interval": "1h", "download_via_proxy_section": "", "auto_user_agent": "1", "user_agent": "", "auto_hwid": "1", "hwid": "", "custom_device_headers": "0", "device_os": "", "ver_os": "", "device_model": "", "device_locale": "", "app_version": "", "accept_language": "", "show_dashboard_metadata": "1", "prefix_nodes": "1", "node_prefix": "Example", "include_urltest_groups": "1", "hide_urltest_group_outbounds": "1", "hide_detour_outbounds": "1" } ]
[rule.proxy1.interfaces]
[ ]
[rule.proxy1.outbound_jsons]
[rule.proxy1.legacy_interface]
[rule.proxy1.legacy_outbound_json]
[rule.proxy1.urltests]
[ { "id": "urltest", "display_name": "Fastest", "check_interval": "3m", "tolerance": "75", "testing_url": "https://www.gstatic.com/generate_204", "idle_timeout": "", "interrupt_exist_connections": "1", "pin_dashboard": "1", "filter_mode": "exclude", "detect_server_country": "1", "include_countries": [ ], "include_outbounds": [ "node-a" ], "include_regex": [ ], "exclude_countries": [ "RU" ], "exclude_outbounds": [ ], "exclude_regex": [ ] } ]
[rule.proxy1.priority_groups]
[ ]
[rule.proxy1.dashboard_filter]
{ "filter_mode": "mixed", "detect_server_country": "country_is", "include_countries": [ "NL" ], "include_outbounds": [ "node-a" ], "include_regex": [ "^A" ], "include_proxy_parameters": "1", "include_protocols": [ "vless" ], "include_transports": [ "ws" ], "include_securities": [ "reality" ], "include_groups": [ "urltest" ], "exclude_countries": [ "RU" ], "exclude_outbounds": [ "node-b" ], "exclude_regex": [ "backup" ], "exclude_proxy_parameters": "1", "exclude_protocols": [ "http" ], "exclude_transports": [ "tcp" ], "exclude_securities": [ "none" ], "exclude_groups": [ "urltest" ] }
[rule.proxy1.urltest_enabled]
1
[rule.proxy1.detect_server_country]
flag_emoji
[rule.proxy1.urltest_check_interval]
3m
[rule.proxy1.urltest_tolerance]
75
[rule.proxy1.urltest_testing_url]
https://www.gstatic.com/generate_204
[rule.proxy1.urltest_filter_mode]
exclude
[rule.proxy1.urltest_exclude_countries]
RU
[rule.proxy1.urltest_include_countries]
[rule.proxy1.urltest_exclude_outbounds]
[rule.proxy1.urltest_exclude_regex]
[rule.proxy1.urltest_include_outbounds]
node-a
[rule.proxy1.urltest_include_regex]
[rule.proxy1.subscription_update_interval]
1h
[rule.proxy1.outbound_detour_enabled]
1
[rule.proxy1.outbound_detour_section]
out1
[rule.proxy1.mixed_proxy_enabled]
1
[rule.proxy1.mixed_proxy_port]
2080
[rule.proxy1.mixed_proxy_auth_enabled]
1
[rule.proxy1.mixed_proxy_username]
user
[rule.proxy1.mixed_proxy_password]
pass
[rule.proxy1.resolve_real_ip_for_routing]
1
[rule.proxy1.domain]
full.example,legacy.example
[rule.proxy1.domain_suffix]
suffix.example
[rule.proxy1.domain_keyword]
ads
[rule.proxy1.domain_regex]
^foo
[rule.proxy1.ip_cidr]
203.0.113.1
[rule.proxy1.source_ip_cidr]
198.51.100.0/24
[rule.proxy1.ports]
443,80,443-444
[rule.proxy1.fully_routed_ips]
198.51.100.2
[rule.proxy1.excluded_ips]
[rule.proxy1.excluded_protocol]
[rule.proxy1.protocol]
[rule.proxy1.community_lists]
telegram
[rule.proxy1.rule_set]
https://example.com/remote.srs
[rule.proxy1.rule_set_with_subnets]
local.srs
[rule.proxy1.domain_ip_lists]
local.lst
[rule.out1.action]
outbound
[rule.out1.connection_urls]
[ ]
[rule.out1.subscription_urls]
[ ]
[rule.out1.interfaces]
[ ]
[rule.out1.outbound_jsons]
[rule.out1.legacy_interface]
[rule.out1.legacy_outbound_json]
{"type":"direct"}
[rule.out1.urltests]
[ ]
[rule.out1.priority_groups]
[ ]
[rule.out1.dashboard_filter]
{ "filter_mode": "disabled", "detect_server_country": "flag_emoji", "include_countries": [ ], "include_outbounds": [ ], "include_regex": [ ], "include_proxy_parameters": "0", "include_protocols": [ ], "include_transports": [ ], "include_securities": [ ], "include_groups": [ ], "exclude_countries": [ ], "exclude_outbounds": [ ], "exclude_regex": [ ], "exclude_proxy_parameters": "0", "exclude_protocols": [ ], "exclude_transports": [ ], "exclude_securities": [ ], "exclude_groups": [ ] }
[rule.out1.urltest_enabled]
0
[rule.out1.detect_server_country]
flag_emoji
[rule.out1.urltest_check_interval]
[rule.out1.urltest_tolerance]
50
[rule.out1.urltest_testing_url]
https://www.gstatic.com/generate_204
[rule.out1.urltest_filter_mode]
disabled
[rule.out1.urltest_exclude_countries]
[rule.out1.urltest_include_countries]
[rule.out1.urltest_exclude_outbounds]
[rule.out1.urltest_exclude_regex]
[rule.out1.urltest_include_outbounds]
[rule.out1.urltest_include_regex]
[rule.out1.subscription_update_interval]
[rule.out1.outbound_detour_enabled]
0
[rule.out1.mixed_proxy_enabled]
0
[rule.out1.resolve_real_ip_for_routing]
0
[rule.out1.domain]
[rule.out1.domain_suffix]
[rule.out1.domain_keyword]
[rule.out1.domain_regex]
[rule.out1.ip_cidr]
[rule.out1.source_ip_cidr]
[rule.out1.ports]
[rule.out1.fully_routed_ips]
[rule.out1.excluded_ips]
[rule.out1.excluded_protocol]
[rule.out1.protocol]
[rule.out1.community_lists]
[rule.out1.rule_set]
[rule.out1.rule_set_with_subnets]
[rule.out1.domain_ip_lists]
[rule.bye1.action]
byedpi
[rule.bye1.byedpi_index]
1
[rule.bye1.mixed_proxy_enabled]
0
[rule.bye1.resolve_real_ip_for_routing]
1
[rule.bye1.domain]
[rule.bye1.domain_suffix]
[rule.bye1.domain_keyword]
[rule.bye1.domain_regex]
[rule.bye1.ip_cidr]
[rule.bye1.source_ip_cidr]
[rule.bye1.ports]
[rule.bye1.fully_routed_ips]
[rule.bye1.excluded_ips]
[rule.bye1.excluded_protocol]
[rule.bye1.protocol]
[rule.bye1.community_lists]
[rule.bye1.rule_set]
[rule.bye1.rule_set_with_subnets]
[rule.bye1.domain_ip_lists]
[rule.vpn1.action]
vpn
[rule.vpn1.connection_urls]
[ ]
[rule.vpn1.subscription_urls]
[ ]
[rule.vpn1.interfaces]
[ { "name": "wg0", "domain_resolver_enabled": "1", "domain_resolver_dns_type": "doh", "domain_resolver_dns_server": "https://dns.example/dns-query" } ]
[rule.vpn1.outbound_jsons]
[rule.vpn1.legacy_interface]
wg0
[rule.vpn1.legacy_outbound_json]
[rule.vpn1.urltests]
[ ]
[rule.vpn1.priority_groups]
[ ]
[rule.vpn1.dashboard_filter]
{ "filter_mode": "disabled", "detect_server_country": "flag_emoji", "include_countries": [ ], "include_outbounds": [ ], "include_regex": [ ], "include_proxy_parameters": "0", "include_protocols": [ ], "include_transports": [ ], "include_securities": [ ], "include_groups": [ ], "exclude_countries": [ ], "exclude_outbounds": [ ], "exclude_regex": [ ], "exclude_proxy_parameters": "0", "exclude_protocols": [ ], "exclude_transports": [ ], "exclude_securities": [ ], "exclude_groups": [ ] }
[rule.vpn1.urltest_enabled]
0
[rule.vpn1.detect_server_country]
flag_emoji
[rule.vpn1.urltest_check_interval]
[rule.vpn1.urltest_tolerance]
50
[rule.vpn1.urltest_testing_url]
https://www.gstatic.com/generate_204
[rule.vpn1.urltest_filter_mode]
disabled
[rule.vpn1.urltest_exclude_countries]
[rule.vpn1.urltest_include_countries]
[rule.vpn1.urltest_exclude_outbounds]
[rule.vpn1.urltest_exclude_regex]
[rule.vpn1.urltest_include_outbounds]
[rule.vpn1.urltest_include_regex]
[rule.vpn1.subscription_update_interval]
[rule.vpn1.outbound_detour_enabled]
0
[rule.vpn1.mixed_proxy_enabled]
0
[rule.vpn1.resolve_real_ip_for_routing]
1
[rule.vpn1.domain]
[rule.vpn1.domain_suffix]
[rule.vpn1.domain_keyword]
[rule.vpn1.domain_regex]
[rule.vpn1.ip_cidr]
[rule.vpn1.source_ip_cidr]
[rule.vpn1.ports]
[rule.vpn1.fully_routed_ips]
[rule.vpn1.excluded_ips]
[rule.vpn1.excluded_protocol]
[rule.vpn1.protocol]
[rule.vpn1.community_lists]
[rule.vpn1.rule_set]
[rule.vpn1.rule_set_with_subnets]
[rule.vpn1.domain_ip_lists]
[server.srv_disabled.enabled]
0
[server.srv1.enabled]
1
[server.srv1.label]
srv1
[server.srv1.protocol]
vless
[server.srv1.listen]
0.0.0.0
[server.srv1.listen_port]
8443
[server.srv1.public_host]
[server.srv1.inbound_json]
[server.srv1.routing_mode]
rules
[server.srv1.routing_section]
[server.srv1.security]
reality
[server.srv1.server_users]
alice bob
[server.srv1.tls_server_name]
example.com
[server.srv1.tls_alpn]
[server.srv1.tls_certificate_path]
[server.srv1.tls_key_path]
[server.srv1.reality_handshake_server]
[server.srv1.reality_handshake_server_port]
[server.srv1.reality_private_key]
[server.srv1.reality_public_key]
[server.srv1.reality_short_id]
[server.srv1.reality_max_time_difference]
[server.srv1.transport]
ws
[server.srv1.transport_path]
/ws
[server.srv1.transport_host]
[server.srv1.transport_service_name]
[server.srv1.transport_hosts]
[server.srv1.transport_xhttp_mode]
[server.srv1.client_fingerprint]
[server.srv1.server_uuid]
[server.srv1.server_username]
[server.srv1.server_password]
[server.srv1.vless_flow]
[server.srv1.vmess_alter_id]
[server.srv1.shadowsocks_method]
[server.srv1.hysteria2_up_mbps]
[server.srv1.hysteria2_down_mbps]
[server.srv1.hysteria2_obfs_type]
[server.srv1.hysteria2_obfs_password]
[server.srv1.tuic_congestion_control]
[server.srv1.tuic_udp_relay_mode]
[server.srv1.tuic_password]
[server.srv1.mtproto_secret]
[server.srv1.mtproto_faketls]
[server.srv1.mtproto_padding]
[server.srv1.mtproto_concurrency]
[server.srv1.mtproto_domain_fronting_port]
[server.srv1.mtproto_domain_fronting_ip]
[server.srv1.mtproto_domain_fronting_proxy_protocol]
[server.srv1.mtproto_prefer_ip]
[server.srv1.mtproto_auto_update]
[server.srv1.mtproto_allow_fallback_on_unknown_dc]
[server.srv1.mtproto_tolerate_time_skewness]
[server.srv1.mtproto_idle_timeout]
[server.srv1.mtproto_handshake_timeout]
[server.srv1.tailscale_auth_key]
[server.srv1.tailscale_control_url]
[server.srv1.tailscale_hostname]
[server.srv1.tailscale_mode]
singbox
[server.srv1.tailscale_masquerade]
1
[server.srv1.tailscale_use_exit_node]
0
[server.srv1.tailscale_accept_routes]
1
[server.srv1.tailscale_advertise_routes]
[server.srv1.tailscale_advertise_exit_node]
EOF_SING_BOX_SIG
assert_eq "$(md5_file "$WORK_DIR/sing-box-signature.expected")" \
"$(state_ucode sing-box-signature-fixture "$WORK_DIR/sing-box-signature.json")" \
"sing-box signature hash"
cat >"$WORK_DIR/dpi-signatures.json" <<'JSON'
{
"section": [
{
".name": "disabled_zap",
".type": "section",
"enabled": "0",
"action": "zapret",
"nfqws_opt": "--disabled"
},
{
".name": "zap1",
".type": "section",
"enabled": "1",
"action": "zapret",
"domain": [ "legacy.example" ],
"domain_suffix": [ "full:full.example", "suffix.example", "keyword:ads" ],
"domain_suffix_text": "full:textfull.example text-suffix.example",
"community_lists": [ "telegram", "youtube" ],
"rule_set": [ "https://example.com/zap.srs" ],
"rule_set_with_subnets": [ "https://example.com/zap-sub.srs" ],
"domain_ip_lists": [ "local.lst", "https://example.com/ip.lst" ],
"user_domain_list_type": "dynamic",
"user_domains": [ "dyn.example" ],
"user_domains_text": "ignored.example",
"local_domain_lists": [ "local-a" ],
"remote_domain_lists": [ "remote-a" ]
},
{
".name": "zap2",
".type": "section",
"enabled": "1",
"action": "zapret2",
"nfqws2_opt": "--z2\n opt",
"domain": [ "z2.example" ],
"domain_suffix": [ "z2-suffix.example" ],
"community_lists": [ "discord" ],
"rule_set": [ "https://example.com/zap2.srs" ],
"rule_set_with_subnets": [ "https://example.com/zap2-sub.srs" ],
"domain_ip_lists": [ "https://example.com/zap2-ip.lst" ],
"user_domain_list_type": "text",
"user_domains": [ "ignored-dyn.example" ],
"user_domains_text": "text.example text2.example",
"local_domain_lists": [ "local-b" ],
"remote_domain_lists": [ "remote-b" ]
},
{
".name": "bye_disabled",
".type": "section",
"enabled": "0",
"action": "byedpi",
"byedpi_cmd_opts": "--disabled"
},
{
".name": "bye1",
".type": "section",
"enabled": "1",
"action": "byedpi"
},
{
".name": "bye2",
".type": "section",
"enabled": "1",
"action": "byedpi",
"byedpi_cmd_opts": "--disorder 3\n--fake-sni example.org"
}
]
}
JSON
cat >"$WORK_DIR/zapret-queue.expected" <<'EOF_ZAPRET_QUEUE'
[zapret_queue.section]
zap1
EOF_ZAPRET_QUEUE
cat >"$WORK_DIR/zapret2-queue.expected" <<'EOF_ZAPRET2_QUEUE'
[zapret2_queue.section]
zap2
EOF_ZAPRET2_QUEUE
cat >"$WORK_DIR/zapret-runtime.expected" <<'EOF_ZAPRET_RUNTIME'
[zapret.zap1.nfqws_opt]
--default-zapret
[zapret.zap1.domain]
textfull.example,full.example,legacy.example
[zapret.zap1.domain_suffix]
text-suffix.example,suffix.example
[zapret.zap1.community_lists]
telegram youtube
[zapret.zap1.rule_set]
https://example.com/zap.srs
[zapret.zap1.rule_set_with_subnets]
https://example.com/zap-sub.srs
[zapret.zap1.domain_ip_lists]
local.lst https://example.com/ip.lst
[zapret.zap1.user_domain_list_type]
dynamic
[zapret.zap1.local_domain_lists]
local-a
[zapret.zap1.remote_domain_lists]
remote-a
[zapret.zap1.user_domains]
dyn.example
EOF_ZAPRET_RUNTIME
cat >"$WORK_DIR/zapret2-runtime.expected" <<'EOF_ZAPRET2_RUNTIME'
[zapret2.zap2.nfqws2_opt]
--z2 opt
[zapret2.zap2.domain]
z2.example
[zapret2.zap2.domain_suffix]
z2-suffix.example
[zapret2.zap2.community_lists]
discord
[zapret2.zap2.rule_set]
https://example.com/zap2.srs
[zapret2.zap2.rule_set_with_subnets]
https://example.com/zap2-sub.srs
[zapret2.zap2.domain_ip_lists]
https://example.com/zap2-ip.lst
[zapret2.zap2.user_domain_list_type]
text
[zapret2.zap2.local_domain_lists]
local-b
[zapret2.zap2.remote_domain_lists]
remote-b
[zapret2.zap2.user_domains]
text.example text2.example
EOF_ZAPRET2_RUNTIME
cat >"$WORK_DIR/byedpi-runtime.expected" <<'EOF_BYEDPI_RUNTIME'
[byedpi.bye1.index]
1
[byedpi.bye1.byedpi_cmd_opts]
--default-bye
[byedpi.bye2.index]
2
[byedpi.bye2.byedpi_cmd_opts]
--disorder 3 --fake-sni example.org
EOF_BYEDPI_RUNTIME
assert_eq "$(md5_file "$WORK_DIR/zapret-queue.expected")" \
"$(state_ucode zapret-queue-signature-fixture "$WORK_DIR/dpi-signatures.json")" \
"zapret queue signature hash"
assert_eq "$(md5_file "$WORK_DIR/zapret2-queue.expected")" \
"$(state_ucode zapret2-queue-signature-fixture "$WORK_DIR/dpi-signatures.json")" \
"zapret2 queue signature hash"
assert_eq "$(md5_file "$WORK_DIR/zapret-runtime.expected")" \
"$(state_ucode zapret-runtime-signature-fixture "$WORK_DIR/dpi-signatures.json")" \
"zapret runtime signature hash"
assert_eq "$(md5_file "$WORK_DIR/zapret2-runtime.expected")" \
"$(state_ucode zapret2-runtime-signature-fixture "$WORK_DIR/dpi-signatures.json")" \
"zapret2 runtime signature hash"
assert_eq "$(md5_file "$WORK_DIR/byedpi-runtime.expected")" \
"$(state_ucode byedpi-runtime-signature-fixture "$WORK_DIR/dpi-signatures.json")" \
"byedpi runtime signature hash"
cat >"$WORK_DIR/reload-state-signatures.json" <<'JSON'
{
"settings": {
"list_update_enabled": "1",
"update_interval": "6h",
"component_update_check_enabled": "1",
"component_update_check_interval": "2h"
},
"section": [
{
".name": "disabled",
".type": "section",
"enabled": "0",
"action": "proxy",
"ports": [ "1" ],
"community_lists": [ "telegram" ],
"remote_domain_lists": [ "https://example.com/disabled-domains.lst" ],
"remote_subnet_lists": [ "https://example.com/disabled-subnets.lst" ],
"rule_set_with_subnets": [ "https://example.com/disabled-subnets.srs" ],
"domain_ip_lists": [ "https://example.com/disabled-mixed.lst" ],
"subscription_urls": [ "https://example.com/disabled-sub.txt" ],
"urltest_enabled": "1"
},
{
".name": "list_proxy",
".type": "section",
"enabled": "1",
"action": "proxy",
"ports": [ "443", "bad" ],
"ports_text": "80,443-444 # comment\nbad-port",
"community_lists": [ "geoblock", "meta", "telegram", "youtube", "discord" ],
"remote_domain_lists": [ "https://example.com/domains.lst", "local-domains" ],
"remote_subnet_lists": [ "https://example.com/subnets.lst" ],
"rule_set_with_subnets": [ "https://example.com/subnets.srs" ],
"domain_ip_lists": [ "local.lst", "https://example.com/mixed.lst" ],
"subscription_urls": [ "https://example.com/sub1.txt", "https://example.com/sub2.txt" ],
"urltest_enabled": "1"
},
{
".name": "sub_paused",
".type": "section",
"enabled": "1",
"action": "proxy",
"subscription_urls": [ "https://example.com/off.txt" ],
"subscription_update_enabled": "0"
},
{
".name": "implicit_action",
".type": "section",
"enabled": "1",
"subscription_urls": [ "https://example.com/implicit.txt" ],
"urltest_enabled": "1"
},
{
".name": "urltest_custom",
".type": "section",
"enabled": "1",
"action": "proxy",
"urltest_enabled": "1",
"urltest_check_interval": "7m"
},
{
".name": "dns_only",
".type": "section",
"enabled": "1",
"action": "dns",
"ports": [ "5353" ],
"community_lists": [ "telegram" ],
"remote_subnet_lists": [ "https://example.com/ignored-subnets.lst" ],
"rule_set_with_subnets": [ "https://example.com/ignored-subnets.srs" ],
"domain_ip_lists": [ "https://example.com/dns-domains.lst" ]
}
]
}
JSON
cat >"$WORK_DIR/list-signature.expected" <<'EOF_LIST_SIG'
[lists.list_proxy.action]
proxy
[lists.list_proxy.ports]
443,80,443-444
[lists.list_proxy.community_subnet_lists]
meta telegram discord
[lists.list_proxy.remote_domain_lists]
https://example.com/domains.lst local-domains
[lists.list_proxy.remote_subnet_lists]
https://example.com/subnets.lst
[lists.list_proxy.rule_set_with_subnets]
https://example.com/subnets.srs
[lists.list_proxy.domain_ip_lists]
local.lst https://example.com/mixed.lst
[lists.sub_paused.action]
proxy
[lists.sub_paused.ports]
[lists.sub_paused.community_subnet_lists]
[lists.sub_paused.remote_domain_lists]
[lists.sub_paused.remote_subnet_lists]
[lists.sub_paused.rule_set_with_subnets]
[lists.sub_paused.domain_ip_lists]
[lists.implicit_action.action]
[lists.implicit_action.ports]
[lists.implicit_action.community_subnet_lists]
[lists.implicit_action.remote_domain_lists]
[lists.implicit_action.remote_subnet_lists]
[lists.implicit_action.rule_set_with_subnets]
[lists.implicit_action.domain_ip_lists]
[lists.urltest_custom.action]
proxy
[lists.urltest_custom.ports]
[lists.urltest_custom.community_subnet_lists]
[lists.urltest_custom.remote_domain_lists]
[lists.urltest_custom.remote_subnet_lists]
[lists.urltest_custom.rule_set_with_subnets]
[lists.urltest_custom.domain_ip_lists]
[lists.dns_only.action]
dns
[lists.dns_only.domain_ip_lists]
https://example.com/dns-domains.lst
EOF_LIST_SIG
cat >"$WORK_DIR/cron-signature.expected" <<'EOF_CRON_SIG'
[settings.update_interval]
6h
[settings.component_update_check_interval]
2h
[lists.list_proxy.action]
proxy
[lists.list_proxy.ports]
443,80,443-444
[lists.list_proxy.community_subnet_lists]
meta telegram discord
[lists.list_proxy.remote_domain_lists]
https://example.com/domains.lst local-domains
[lists.list_proxy.remote_subnet_lists]
https://example.com/subnets.lst
[lists.list_proxy.rule_set_with_subnets]
https://example.com/subnets.srs
[lists.list_proxy.domain_ip_lists]
local.lst https://example.com/mixed.lst
[lists.sub_paused.action]
proxy
[lists.sub_paused.ports]
[lists.sub_paused.community_subnet_lists]
[lists.sub_paused.remote_domain_lists]
[lists.sub_paused.remote_subnet_lists]
[lists.sub_paused.rule_set_with_subnets]
[lists.sub_paused.domain_ip_lists]
[lists.implicit_action.action]
[lists.implicit_action.ports]
[lists.implicit_action.community_subnet_lists]
[lists.implicit_action.remote_domain_lists]
[lists.implicit_action.remote_subnet_lists]
[lists.implicit_action.rule_set_with_subnets]
[lists.implicit_action.domain_ip_lists]
[lists.urltest_custom.action]
proxy
[lists.urltest_custom.ports]
[lists.urltest_custom.community_subnet_lists]
[lists.urltest_custom.remote_domain_lists]
[lists.urltest_custom.remote_subnet_lists]
[lists.urltest_custom.rule_set_with_subnets]
[lists.urltest_custom.domain_ip_lists]
[lists.dns_only.action]
dns
[lists.dns_only.domain_ip_lists]
https://example.com/dns-domains.lst
[subscription.list_proxy.subscription_urls]
[ { "url": "https://example.com/sub1.txt", "subscription_update_enabled": "1", "subscription_update_interval": "1h", "download_via_proxy_section": "", "auto_user_agent": "1", "user_agent": "", "auto_hwid": "1", "hwid": "", "custom_device_headers": "0", "device_os": "", "ver_os": "", "device_model": "", "device_locale": "", "app_version": "", "accept_language": "", "show_dashboard_metadata": "1", "prefix_nodes": "0", "node_prefix": "", "include_urltest_groups": "1", "hide_urltest_group_outbounds": "1", "hide_detour_outbounds": "1" }, { "url": "https://example.com/sub2.txt", "subscription_update_enabled": "1", "subscription_update_interval": "1h", "download_via_proxy_section": "", "auto_user_agent": "1", "user_agent": "", "auto_hwid": "1", "hwid": "", "custom_device_headers": "0", "device_os": "", "ver_os": "", "device_model": "", "device_locale": "", "app_version": "", "accept_language": "", "show_dashboard_metadata": "1", "prefix_nodes": "0", "node_prefix": "", "include_urltest_groups": "1", "hide_urltest_group_outbounds": "1", "hide_detour_outbounds": "1" } ]
[subscription.list_proxy.subscription_update_interval]
1h
[subscription.sub_paused.subscription_urls]
[ { "url": "https://example.com/off.txt", "subscription_update_enabled": "0", "subscription_update_interval": "1h", "download_via_proxy_section": "", "auto_user_agent": "1", "user_agent": "", "auto_hwid": "1", "hwid": "", "custom_device_headers": "0", "device_os": "", "ver_os": "", "device_model": "", "device_locale": "", "app_version": "", "accept_language": "", "show_dashboard_metadata": "1", "prefix_nodes": "0", "node_prefix": "", "include_urltest_groups": "1", "hide_urltest_group_outbounds": "1", "hide_detour_outbounds": "1" } ]
[subscription.sub_paused.subscription_update_interval]
EOF_CRON_SIG
assert_eq "$(md5_file "$WORK_DIR/list-signature.expected")" \
"$(state_ucode list-update-signature-fixture "$WORK_DIR/reload-state-signatures.json")" \
"list update signature hash"
assert_eq "$(md5_file "$WORK_DIR/cron-signature.expected")" \
"$(state_ucode cron-signature-fixture "$WORK_DIR/reload-state-signatures.json")" \
"cron signature hash"
nft_signature="$(nft_ucode nft-runtime-signature-fixture "$WORK_DIR/reload-state-signatures.json")"
assert_eq "$nft_signature" \
"$(state_ucode nft-signature-fixture "$WORK_DIR/reload-state-signatures.json")" \
"state reload nft signature parity"
assert_eq "list_proxy urltest_custom" \
"$(state_ucode urltest-enabled-sections-fixture "$WORK_DIR/reload-state-signatures.json")" \
"urltest enabled sections"
assert_eq "6h" \
"$(state_ucode settings-update-interval-fixture "$WORK_DIR/reload-state-signatures.json")" \
"settings update interval"
assert_eq "1h" \
"$(state_ucode subscription-update-interval-fixture "$WORK_DIR/reload-state-signatures.json" list_proxy)" \
"default subscription update interval"
assert_eq "" \
"$(state_ucode subscription-update-interval-fixture "$WORK_DIR/reload-state-signatures.json" sub_paused)" \
"disabled subscription update interval"
assert_eq "3m" \
"$(state_ucode urltest-check-interval-fixture "$WORK_DIR/reload-state-signatures.json" list_proxy)" \
"default urltest check interval"
assert_eq "7m" \
"$(state_ucode urltest-check-interval-fixture "$WORK_DIR/reload-state-signatures.json" urltest_custom)" \
"custom urltest check interval"
state_ucode has-subscription-update-sources-fixture "$WORK_DIR/reload-state-signatures.json" >/dev/null ||
fail "fixture should have subscription update sources"
cat >"$WORK_DIR/dns-action-signature-udp.json" <<'JSON'
{
"section": [
{ ".name": "dns", ".type": "section", "enabled": "1", "action": "dns", "dns_type": "udp", "dns_server": "9.9.9.9", "domain_suffix": [ "example.org" ] }
]
}
JSON
cat >"$WORK_DIR/dns-action-signature-dot.json" <<'JSON'
{
"section": [
{ ".name": "dns", ".type": "section", "enabled": "1", "action": "dns", "dns_type": "dot", "dns_server": "9.9.9.9", "domain_suffix": [ "example.org" ] }
]
}
JSON
if [ "$(state_ucode sing-box-signature-fixture "$WORK_DIR/dns-action-signature-udp.json")" = \
"$(state_ucode sing-box-signature-fixture "$WORK_DIR/dns-action-signature-dot.json")" ]; then
fail "DNS action protocol should change the sing-box signature"
fi
cat >"$WORK_DIR/reload-state.expected" <<EOF_RELOAD_STATE
format=1
service_trigger_signature=$(state_ucode service-trigger-signature-fixture "$WORK_DIR/reload-state-signatures.json")
dnsmasq_signature=$(state_ucode dnsmasq-signature-fixture "$WORK_DIR/reload-state-signatures.json")
sing_box_signature=$(state_ucode sing-box-signature-fixture "$WORK_DIR/reload-state-signatures.json")
nft_signature=$nft_signature
zapret_queue_signature=$(state_ucode zapret-queue-signature-fixture "$WORK_DIR/reload-state-signatures.json")
zapret_runtime_signature=$(state_ucode zapret-runtime-signature-fixture "$WORK_DIR/reload-state-signatures.json")
zapret2_queue_signature=$(state_ucode zapret2-queue-signature-fixture "$WORK_DIR/reload-state-signatures.json")
zapret2_runtime_signature=$(state_ucode zapret2-runtime-signature-fixture "$WORK_DIR/reload-state-signatures.json")
byedpi_runtime_signature=$(state_ucode byedpi-runtime-signature-fixture "$WORK_DIR/reload-state-signatures.json")
list_signature=$(state_ucode list-update-signature-fixture "$WORK_DIR/reload-state-signatures.json")
hosts_list_signature=$(state_ucode hosts-list-signature-fixture "$WORK_DIR/reload-state-signatures.json")
cron_signature=$(state_ucode cron-signature-fixture "$WORK_DIR/reload-state-signatures.json")
urltest_enabled_sections=list_proxy urltest_custom
dont_touch_dhcp=$(state_ucode dont-touch-dhcp-fixture "$WORK_DIR/reload-state-signatures.json")
EOF_RELOAD_STATE
assert_eq "$(cat "$WORK_DIR/reload-state.expected")" \
"$(state_ucode reload-state-text-fixture "$WORK_DIR/reload-state-signatures.json" 1)" \
"reload state aggregate text"
cat >"$WORK_DIR/disabled-update-interval.json" <<'JSON'
{
"settings": {
"list_update_enabled": "0",
"update_interval": "6h"
}
}
JSON
assert_eq "" \
"$(state_ucode settings-update-interval-fixture "$WORK_DIR/disabled-update-interval.json")" \
"disabled settings update interval"
cat >"$WORK_DIR/empty-update-interval.json" <<'JSON'
{
"settings": {
"list_update_enabled": "1",
"update_interval": ""
}
}
JSON
assert_eq "1d" \
"$(state_ucode settings-update-interval-fixture "$WORK_DIR/empty-update-interval.json")" \
"empty settings update interval default"
cat >"$WORK_DIR/no-subscription-source.json" <<'JSON'
{
"section": [
{
".name": "implicit_action",
".type": "section",
"enabled": "1",
"subscription_urls": [ "https://example.com/implicit.txt" ]
},
{
".name": "paused",
".type": "section",
"enabled": "1",
"action": "proxy",
"subscription_urls": [ "https://example.com/off.txt" ],
"subscription_update_enabled": "0"
}
]
}
JSON
if state_ucode has-subscription-update-sources-fixture "$WORK_DIR/no-subscription-source.json" >/dev/null 2>&1; then
fail "implicit or disabled subscription updates should not require subscription update"
fi
cat >"$WORK_DIR/source-detection.json" <<'JSON'
{
"section": [
{
".name": "disabled",
".type": "section",
"enabled": "0",
"community_lists": [ "telegram" ],
"remote_domain_lists": [ "https://example.com/disabled-domains.lst" ],
"remote_subnet_lists": [ "https://example.com/disabled-subnets.lst" ],
"rule_set": [ "https://example.com/disabled.srs" ],
"domain_ip_lists": [ "https://example.com/disabled-mixed.lst" ]
},
{
".name": "general",
".type": "section",
"enabled": "1",
"remote_domain_lists": [ "https://example.com/domains.lst" ]
},
{
".name": "nft",
".type": "section",
"remote_subnet_lists": [ "https://example.com/subnets.lst" ]
},
{
".name": "remote_ruleset",
".type": "section",
"enabled": "1",
"rule_set": [ "/tmp/local.srs", "https://example.com/remote.srs" ]
}
]
}
JSON
state_ucode has-list-update-sources-fixture "$WORK_DIR/source-detection.json" >/dev/null ||
fail "fixture should have general list update sources"
state_ucode has-nft-list-update-sources-fixture "$WORK_DIR/source-detection.json" >/dev/null ||
fail "fixture should have nft list update sources"
state_ucode has-remote-sing-box-ruleset-sources-fixture "$WORK_DIR/source-detection.json" >/dev/null ||
fail "fixture should have remote sing-box ruleset sources"
cat >"$WORK_DIR/general-only-source.json" <<'JSON'
{
"section": [
{
".name": "general",
".type": "section",
"remote_domain_lists": [ "https://example.com/domains.lst" ]
}
]
}
JSON
state_ucode has-list-update-sources-fixture "$WORK_DIR/general-only-source.json" >/dev/null ||
fail "remote domain list should require general list update"
if state_ucode has-nft-list-update-sources-fixture "$WORK_DIR/general-only-source.json" >/dev/null 2>&1; then
fail "remote domain list alone should not require nft list update"
fi
cat >"$WORK_DIR/disabled-only-source.json" <<'JSON'
{
"section": [
{
".name": "disabled",
".type": "section",
"enabled": "0",
"community_lists": [ "telegram" ],
"remote_domain_lists": [ "https://example.com/domains.lst" ],
"remote_subnet_lists": [ "https://example.com/subnets.lst" ],
"rule_set": [ "https://example.com/remote.srs" ],
"rule_set_with_subnets": [ "https://example.com/subnets.srs" ],
"domain_ip_lists": [ "https://example.com/mixed.lst" ]
}
]
}
JSON
if state_ucode has-list-update-sources-fixture "$WORK_DIR/disabled-only-source.json" >/dev/null 2>&1; then
fail "disabled rule should not require general list update"
fi
if state_ucode has-nft-list-update-sources-fixture "$WORK_DIR/disabled-only-source.json" >/dev/null 2>&1; then
fail "disabled rule should not require nft list update"
fi
if state_ucode has-remote-sing-box-ruleset-sources-fixture "$WORK_DIR/disabled-only-source.json" >/dev/null 2>&1; then
fail "disabled rule should not require remote sing-box ruleset refresh"
fi
printf 'runtime state predicate checks passed\n'