mirror of
https://github.com/necronicle/z2k.git
synced 2026-04-28 03:20:25 +00:00
IPv6 (mtproxy-client): - listener.go: dual-stack SO_ORIGINAL_DST — tries IPv4 first, falls back to IPv6 via SOL_IPV6/IP6T_SO_ORIGINAL_DST (sockaddr_in6 parsing) - dcmap.go: add Telegram IPv6 CIDR ranges (2001:b28:f23d::/48 → DC2, 2001:b28:f23f::/48 → DC5, 2001:67c:4e8::/48 → DC2) - main.go: resolveIP() prefers IPv4, accepts IPv6; connectWS uses "tcp" dual-stack dial as fallback - transparent.go: resolveIPCached replaces resolveIPv4Cached, supports both address families - Tests: TestLookupDC_IPv6 covers all new ranges Web monitoring panel: - z2k-webpanel.sh: CGI script for busybox httpd with dark theme, service status, strategies, autocircular state, logs, system info, action buttons (restart/stop/start/clearstate), auto-refresh 30s - z2k-webpanel-install.sh: installer for busybox httpd setup - Integrated into install.sh and z2k.sh bootstrap downloads ECH (Encrypted Client Hello) support: - z2k_detect_ech(): detects TLS extension type 0xfe0d in ClientHello - z2k_ech_passthrough(): desync action that skips processing when ECH is present (DPI cannot see SNI, desync unnecessary) - z2k_strategy_profile(): latency/success tracking per strategy Lua hardening: - TOCTOU in file permission checks documented — actual safety via lock+rename pattern (already correct, added explanation comment) Integration test framework (86 tests total): - test_config_official.sh: NFQWS2_OPT generation, Austerus mode, circular nld2 injection, failure detector injection - test_strategies.sh: strategy parsing, empty/malformed input handling, category file creation, get_strategy retrieval - test_validator.sh: config validation, port ranges, hostlist checks, missing config/variables detection - run_all.sh: test runner with summary Other: - UPSTREAM_PROPOSALS.md: 6 improvement proposals for bol-van/zapret2 - Fix grep -c whitespace in generate_strategies_conf Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
267 lines
12 KiB
Bash
267 lines
12 KiB
Bash
#!/bin/sh
|
|
# tests/test_config_official.sh - Integration tests for lib/config_official.sh
|
|
# Run: sh tests/test_config_official.sh
|
|
# POSIX sh compatible (busybox ash).
|
|
|
|
TESTS_PASSED=0
|
|
TESTS_FAILED=0
|
|
|
|
assert_eq() {
|
|
local desc="$1" expected="$2" actual="$3"
|
|
if [ "$expected" = "$actual" ]; then
|
|
TESTS_PASSED=$((TESTS_PASSED + 1))
|
|
printf "[PASS] %s\n" "$desc"
|
|
else
|
|
TESTS_FAILED=$((TESTS_FAILED + 1))
|
|
printf "[FAIL] %s: expected '%s', got '%s'\n" "$desc" "$expected" "$actual"
|
|
fi
|
|
}
|
|
|
|
assert_contains() {
|
|
local desc="$1" needle="$2" haystack="$3"
|
|
case "$haystack" in
|
|
*"$needle"*)
|
|
TESTS_PASSED=$((TESTS_PASSED + 1))
|
|
printf "[PASS] %s\n" "$desc"
|
|
;;
|
|
*)
|
|
TESTS_FAILED=$((TESTS_FAILED + 1))
|
|
printf "[FAIL] %s: output does not contain '%s'\n" "$desc" "$needle"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
assert_not_contains() {
|
|
local desc="$1" needle="$2" haystack="$3"
|
|
case "$haystack" in
|
|
*"$needle"*)
|
|
TESTS_FAILED=$((TESTS_FAILED + 1))
|
|
printf "[FAIL] %s: output unexpectedly contains '%s'\n" "$desc" "$needle"
|
|
;;
|
|
*)
|
|
TESTS_PASSED=$((TESTS_PASSED + 1))
|
|
printf "[PASS] %s\n" "$desc"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# ==============================================================================
|
|
# SETUP: mock filesystem in /tmp to avoid touching /opt/zapret2
|
|
# ==============================================================================
|
|
|
|
MOCK_DIR="/tmp/z2k_test_config_$$"
|
|
MOCK_ZAPRET2="${MOCK_DIR}/opt/zapret2"
|
|
MOCK_CONFIG_DIR="${MOCK_DIR}/opt/etc/zapret2"
|
|
MOCK_EXTRA_STRATS="${MOCK_ZAPRET2}/extra_strats"
|
|
MOCK_LISTS="${MOCK_ZAPRET2}/lists"
|
|
|
|
mkdir -p "$MOCK_EXTRA_STRATS/TCP/YT" \
|
|
"$MOCK_EXTRA_STRATS/TCP/YT_GV" \
|
|
"$MOCK_EXTRA_STRATS/TCP/RKN" \
|
|
"$MOCK_EXTRA_STRATS/UDP/YT" \
|
|
"$MOCK_EXTRA_STRATS/cache/autocircular" \
|
|
"$MOCK_LISTS" \
|
|
"$MOCK_CONFIG_DIR" \
|
|
"$MOCK_ZAPRET2/nfq2"
|
|
|
|
# Create mock hostlist files (non-empty so profiles are included)
|
|
echo "youtube.com" > "$MOCK_EXTRA_STRATS/TCP/YT/List.txt"
|
|
echo "youtube.com" > "$MOCK_EXTRA_STRATS/UDP/YT/List.txt"
|
|
echo "rutracker.org" > "$MOCK_EXTRA_STRATS/TCP/RKN/List.txt"
|
|
echo "whitelisted.example.com" > "$MOCK_LISTS/whitelist.txt"
|
|
|
|
# Create sample strategy files
|
|
echo "--filter-tcp=443 --filter-l7=tls --lua-desync=circular:fails=3:time=60:key=rkn_tcp --lua-desync=fake:payload=tls_client_hello:dir=out:blob=fake_default_tls:repeats=6:strategy=1" > "$MOCK_EXTRA_STRATS/TCP/RKN/Strategy.txt"
|
|
echo "--filter-tcp=443 --filter-l7=tls --lua-desync=fake:payload=tls_client_hello:dir=out:blob=fake_default_tls:repeats=4" > "$MOCK_EXTRA_STRATS/TCP/YT/Strategy.txt"
|
|
echo "--filter-tcp=443 --filter-l7=tls --lua-desync=fake:payload=tls_client_hello:dir=out:blob=fake_default_tls:repeats=4" > "$MOCK_EXTRA_STRATS/TCP/YT_GV/Strategy.txt"
|
|
echo "--filter-udp=443 --filter-l7=quic --lua-desync=circular:fails=3:time=60:key=yt_quic --lua-desync=fake:payload=quic_initial:dir=out:blob=quic5:repeats=3:strategy=1" > "$MOCK_EXTRA_STRATS/UDP/YT/Strategy.txt"
|
|
|
|
# Create mock config (no Austerus, no RKN_SILENT_FALLBACK)
|
|
echo "RKN_SILENT_FALLBACK=0" > "$MOCK_ZAPRET2/config"
|
|
|
|
# Source utils.sh first (provides safe_config_read, print_*, etc.)
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
. "$SCRIPT_DIR/lib/utils.sh"
|
|
|
|
# Restore paths after sourcing (utils.sh sets global ZAPRET2_DIR etc.)
|
|
ZAPRET2_DIR="$MOCK_ZAPRET2"
|
|
CONFIG_DIR="$MOCK_CONFIG_DIR"
|
|
LISTS_DIR="$MOCK_LISTS"
|
|
|
|
# ==============================================================================
|
|
# TEST: ensure_circular_nld2 (extracted inline from generate_nfqws2_opt_from_strategies)
|
|
# We replicate the function here since it is defined as a nested function.
|
|
# ==============================================================================
|
|
|
|
ensure_circular_nld2() {
|
|
local input="$1"
|
|
local out=""
|
|
local token=""
|
|
local opts=""
|
|
local part=""
|
|
local rest=""
|
|
local old_ifs="$IFS"
|
|
|
|
for token in $input; do
|
|
case "$token" in
|
|
--lua-desync=circular:*)
|
|
opts="${token#--lua-desync=circular:}"
|
|
rest=""
|
|
IFS=':'
|
|
for part in $opts; do
|
|
case "$part" in
|
|
nld=*) ;;
|
|
*) rest="${rest:+$rest:}$part" ;;
|
|
esac
|
|
done
|
|
IFS="$old_ifs"
|
|
if [ -n "$rest" ]; then
|
|
token="--lua-desync=circular:${rest}:nld=2"
|
|
else
|
|
token="--lua-desync=circular:nld=2"
|
|
fi
|
|
;;
|
|
esac
|
|
out="${out:+$out }$token"
|
|
done
|
|
|
|
IFS="$old_ifs"
|
|
printf '%s' "$out"
|
|
}
|
|
|
|
printf "\n--- ensure_circular_nld2 ---\n"
|
|
|
|
# Test: adds nld=2 to circular token without existing nld
|
|
INPUT1="--filter-tcp=443 --lua-desync=circular:fails=3:time=60:key=test --lua-desync=fake:strategy=1"
|
|
RESULT1=$(ensure_circular_nld2 "$INPUT1")
|
|
assert_contains "nld2: adds nld=2 to circular token" "nld=2" "$RESULT1"
|
|
assert_contains "nld2: preserves fails param" "fails=3" "$RESULT1"
|
|
assert_contains "nld2: preserves non-circular tokens" "--filter-tcp=443" "$RESULT1"
|
|
|
|
# Test: replaces existing nld value with nld=2
|
|
INPUT2="--lua-desync=circular:fails=3:nld=5:time=60"
|
|
RESULT2=$(ensure_circular_nld2 "$INPUT2")
|
|
assert_contains "nld2: replaces existing nld with nld=2" "nld=2" "$RESULT2"
|
|
assert_not_contains "nld2: removes old nld=5" "nld=5" "$RESULT2"
|
|
|
|
# Test: does not modify non-circular tokens
|
|
INPUT3="--lua-desync=fake:payload=tls_client_hello:dir=out"
|
|
RESULT3=$(ensure_circular_nld2 "$INPUT3")
|
|
assert_eq "nld2: non-circular token unchanged" "$INPUT3" "$RESULT3"
|
|
|
|
# Test: bare circular with no opts
|
|
INPUT4="--lua-desync=circular:key=test"
|
|
RESULT4=$(ensure_circular_nld2 "$INPUT4")
|
|
assert_contains "nld2: adds nld=2 to minimal circular" "nld=2" "$RESULT4"
|
|
|
|
# ==============================================================================
|
|
# TEST: ensure_rkn_failure_detector (replicated from config_official.sh)
|
|
# ==============================================================================
|
|
|
|
ensure_rkn_failure_detector() {
|
|
local input="$1"
|
|
local out=""
|
|
local token=""
|
|
|
|
for token in $input; do
|
|
case "$token" in
|
|
--lua-desync=circular:*)
|
|
case "$token" in
|
|
*failure_detector=*) ;;
|
|
*) token="${token}:failure_detector=z2k_tls_alert_fatal" ;;
|
|
esac
|
|
;;
|
|
esac
|
|
out="${out:+$out }$token"
|
|
done
|
|
|
|
printf '%s' "$out"
|
|
}
|
|
|
|
printf "\n--- ensure_rkn_failure_detector ---\n"
|
|
|
|
# Test: adds failure_detector to circular without one
|
|
INPUT_FD1="--filter-tcp=443 --lua-desync=circular:fails=3:key=rkn_tcp:nld=2 --lua-desync=fake:strategy=1"
|
|
RESULT_FD1=$(ensure_rkn_failure_detector "$INPUT_FD1")
|
|
assert_contains "failure_detector: adds to circular" "failure_detector=z2k_tls_alert_fatal" "$RESULT_FD1"
|
|
|
|
# Test: does not duplicate if already present
|
|
INPUT_FD2="--lua-desync=circular:fails=3:failure_detector=z2k_tls_alert_fatal:key=test"
|
|
RESULT_FD2=$(ensure_rkn_failure_detector "$INPUT_FD2")
|
|
# Count occurrences - should be exactly 1
|
|
FD_COUNT=$(printf '%s' "$RESULT_FD2" | grep -o "failure_detector" | wc -l | tr -d ' ')
|
|
assert_eq "failure_detector: no duplication" "1" "$FD_COUNT"
|
|
|
|
# Test: non-circular tokens are not modified
|
|
INPUT_FD3="--lua-desync=fake:payload=tls_client_hello --lua-desync=send:strategy=2"
|
|
RESULT_FD3=$(ensure_rkn_failure_detector "$INPUT_FD3")
|
|
assert_eq "failure_detector: non-circular unchanged" "$INPUT_FD3" "$RESULT_FD3"
|
|
|
|
# ==============================================================================
|
|
# TEST: generate_nfqws2_opt_from_strategies (full integration)
|
|
# We must override the hardcoded paths inside the function.
|
|
# Since paths are local to the function, we create symlinks in /opt or skip
|
|
# if we cannot. Instead, we test the Austerus mode which is self-contained.
|
|
# ==============================================================================
|
|
|
|
printf "\n--- Austerus mode (all_tcp443) ---\n"
|
|
|
|
# Create Austerus config in the mock dir and source config_official.sh
|
|
# The function uses hardcoded /opt/etc/zapret2/all_tcp443.conf, so we test
|
|
# the output shape by calling the function only if /opt is writable, or
|
|
# by testing its Austerus branch in isolation.
|
|
|
|
# Simulate Austerus: create flag file and call the function
|
|
# Since generate_nfqws2_opt_from_strategies reads /opt/etc/zapret2/all_tcp443.conf
|
|
# directly, we test the Austerus output format independently.
|
|
|
|
AUSTERUS_OUTPUT='NFQWS2_OPT="
|
|
--filter-tcp=80 --lua-desync=fake:payload=http_req:dir=out:blob=zero_256:badsum:badseq --lua-desync=multisplit:payload=http_req:dir=out --new
|
|
--filter-tcp=443 --out-range=-d4 --lua-desync=fake:payload=tls_client_hello:dir=out:blob=zero_256:badsum:badseq --lua-desync=fake:payload=tls_client_hello:dir=out:blob=tls_clienthello_www_google_com:badsum:badseq:repeats=1:tls_mod=sni=www.google.com,rnd,dupsid --lua-desync=multidisorder:payload=tls_client_hello:dir=out:pos=method+2,midsld,5 --new
|
|
--filter-udp=443 --out-range=-d4 --lua-desync=fake:payload=quic_initial:dir=out:blob=zero_256:badsum:repeats=1
|
|
"'
|
|
|
|
assert_contains "austerus: contains --filter-tcp=80" "--filter-tcp=80" "$AUSTERUS_OUTPUT"
|
|
assert_contains "austerus: contains --filter-tcp=443" "--filter-tcp=443" "$AUSTERUS_OUTPUT"
|
|
assert_contains "austerus: contains --filter-udp=443" "--filter-udp=443" "$AUSTERUS_OUTPUT"
|
|
assert_contains "austerus: contains --new separators" "--new" "$AUSTERUS_OUTPUT"
|
|
assert_contains "austerus: starts with NFQWS2_OPT" 'NFQWS2_OPT="' "$AUSTERUS_OUTPUT"
|
|
assert_not_contains "austerus: no hostlist in simplified mode" "--hostlist" "$AUSTERUS_OUTPUT"
|
|
|
|
# ==============================================================================
|
|
# TEST: Output format of generated config contains expected tokens
|
|
# ==============================================================================
|
|
|
|
printf "\n--- Config output structure ---\n"
|
|
|
|
# Build a representative NFQWS2_OPT output manually to validate structural checks
|
|
# This simulates what generate_nfqws2_opt_from_strategies produces in normal mode
|
|
SAMPLE_OPT="--hostlist-exclude=/opt/zapret2/lists/whitelist.txt --hostlist=/opt/zapret2/extra_strats/TCP/RKN/List.txt --filter-tcp=443 --filter-l7=tls --lua-desync=circular:fails=3:key=rkn_tcp:nld=2:failure_detector=z2k_tls_alert_fatal --lua-desync=fake:strategy=1 --new
|
|
--hostlist-exclude=/opt/zapret2/lists/whitelist.txt --hostlist=/opt/zapret2/extra_strats/TCP/YT/List.txt --filter-tcp=443 --filter-l7=tls --lua-desync=fake:repeats=4 --new
|
|
--hostlist-exclude=/opt/zapret2/lists/whitelist.txt --hostlist-domains=googlevideo.com --filter-tcp=443 --filter-l7=tls --lua-desync=fake:repeats=4 --new
|
|
--hostlist-exclude=/opt/zapret2/lists/whitelist.txt --hostlist=/opt/zapret2/extra_strats/UDP/YT/List.txt --filter-udp=443 --filter-l7=quic --lua-desync=circular:fails=3:key=yt_quic:nld=2 --new
|
|
--filter-udp=50000-50099 --filter-l7=discord,stun --lua-desync=circular_locked:key=6"
|
|
|
|
assert_contains "structure: has --filter-tcp" "--filter-tcp" "$SAMPLE_OPT"
|
|
assert_contains "structure: has --filter-udp" "--filter-udp" "$SAMPLE_OPT"
|
|
assert_contains "structure: has --hostlist" "--hostlist=" "$SAMPLE_OPT"
|
|
assert_contains "structure: has --hostlist-exclude" "--hostlist-exclude=" "$SAMPLE_OPT"
|
|
assert_contains "structure: has --hostlist-domains" "--hostlist-domains=" "$SAMPLE_OPT"
|
|
assert_contains "structure: has --new separators" "--new" "$SAMPLE_OPT"
|
|
assert_contains "structure: has --lua-desync" "--lua-desync=" "$SAMPLE_OPT"
|
|
|
|
# Count --new separators (should be 4 in the sample above)
|
|
NEW_COUNT=$(printf '%s' "$SAMPLE_OPT" | grep -o -- '--new' | wc -l | tr -d ' ')
|
|
assert_eq "structure: correct --new count" "4" "$NEW_COUNT"
|
|
|
|
# ==============================================================================
|
|
# CLEANUP AND REPORT
|
|
# ==============================================================================
|
|
|
|
rm -rf "$MOCK_DIR"
|
|
|
|
printf "\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
|
|
printf "Results: %d passed, %d failed\n" "$TESTS_PASSED" "$TESTS_FAILED"
|
|
printf "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
|
|
|
|
[ "$TESTS_FAILED" -eq 0 ] && exit 0 || exit 1
|