fix(singbox): AWG-джанк в тегах и MTProto-секрет
- i1–i5/j1–j3: классический hex конвертируется в теги <b 0x…>; userspace WireGuard extended/lx молча игнорирует голый hex, handshake не завершался (#39 #41 #42) - j1/j2/j3/itime эмитятся только для extended ≤2.6.0: в 2.6.1+ поля удалены из схемы, sing-box падает на unknown fields - j1–j3 больше не ломаются uci_bin_to_hex - h1–h4: диапазоны «min-max» на обеих ветках - MTProto: секрет канонизируется в ee-hex из любой формы (hex/base64), валидатор даёт понятную ошибку вместо FATAL decode psk (#23) - XHTTP v2: парсинг и экспорт ключей подписок - валидатор: warp/masque/openvpn/snell/mieru/sudoku — только extended - тесты: stat→ucode (busybox), раннеры исключены из E2E, новые server_mtproto.sh и awg_variant_schema.sh
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
TACHYON_LIB="$ROOT_DIR/tachyon/files/usr/lib"
|
||||
WORK_DIR="$(mktemp -d)"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$WORK_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ---- 1. Schema boundary helper (j1/j2/j3/itime removed in extended 2.6.1)
|
||||
cat >"$WORK_DIR/check_schema.uc" <<'UC'
|
||||
let common = require("core.common");
|
||||
printf("%s", common.extended_awg_schema_has_junk_signatures(ARGV[1]) ? "yes" : "no");
|
||||
UC
|
||||
|
||||
check() {
|
||||
ucode -L "$TACHYON_LIB" "$WORK_DIR/check_schema.uc" x "$1"
|
||||
}
|
||||
|
||||
[ "$(check '1.13.16-extended-2.6.0')" = "yes" ] || fail "2.6.0 must still accept j1-j3/itime"
|
||||
[ "$(check '1.13.14-extended-2.5.3')" = "yes" ] || fail "2.5.x must accept j1-j3/itime"
|
||||
[ "$(check '1.13.16-extended-2.6.1')" = "no" ] || fail "2.6.1 removed j1-j3/itime"
|
||||
[ "$(check '1.13.18-extended-2.6.5')" = "no" ] || fail "2.6.5 removed j1-j3/itime"
|
||||
[ "$(check '1.3.0-extended-3.0.0')" = "no" ] || fail "3.x must not emit j1-j3/itime"
|
||||
[ "$(check '1.12.0')" = "no" ] || fail "stock sing-box has no junk-signature fields"
|
||||
[ "$(check '')" = "no" ] || fail "unknown version must not emit j1-j3/itime"
|
||||
|
||||
# ---- 2. Generator omits amnezia.j1 for extended 2.6.5 but keeps it for 2.6.0
|
||||
make_fixture() {
|
||||
cat >"$WORK_DIR/awg_fixture.json" <<JSON
|
||||
{
|
||||
"settings": {
|
||||
".name": "settings",
|
||||
".type": "settings",
|
||||
"enabled": "1"
|
||||
},
|
||||
"section": [
|
||||
{
|
||||
".name": "my_awg",
|
||||
".type": "section",
|
||||
"enabled": "1",
|
||||
"action": "awg",
|
||||
"awg_private_key": "aW5ib3VuZF9wcml2YXRlX2tleQ==",
|
||||
"awg_peer_public_key": "cGVlcl9wdWJsaWNfa2V5",
|
||||
"awg_server_address": "192.168.1.100",
|
||||
"awg_server_port": "51820",
|
||||
"awg_local_address": ["10.0.0.1/24"],
|
||||
"awg_j1": "<b 0x0102030405>",
|
||||
"awg_itime": "30"
|
||||
}
|
||||
]
|
||||
}
|
||||
JSON
|
||||
}
|
||||
|
||||
generate() {
|
||||
mkdir -p "$WORK_DIR/out.section-cache"
|
||||
SB_VERSION_STATE_FILE="$WORK_DIR/sing-box-version" \
|
||||
ucode -L "$TACHYON_LIB" "$TACHYON_LIB/singbox/generator.uc" generate-config-fixture \
|
||||
"$WORK_DIR/awg_fixture.json" "$WORK_DIR/singbox_awg.json" "127.0.0.1" 0 0
|
||||
}
|
||||
|
||||
# 2.6.5: fields removed upstream -> must not appear in the generated config.
|
||||
printf '1.13.18-extended-2.6.5\n' >"$WORK_DIR/sing-box-version"
|
||||
make_fixture
|
||||
generate
|
||||
grep -q '"amnezia"' "$WORK_DIR/singbox_awg.json" || fail "extended endpoint must keep the amnezia object"
|
||||
grep -q '"j1"' "$WORK_DIR/singbox_awg.json" && fail "generated config must not carry amnezia.j1 on extended 2.6.5"
|
||||
grep -q '"itime"' "$WORK_DIR/singbox_awg.json" && fail "generated config must not carry amnezia.itime on extended 2.6.5"
|
||||
|
||||
# 2.6.0: schema still knows the fields -> they are emitted verbatim.
|
||||
printf '1.13.16-extended-2.6.0\n' >"$WORK_DIR/sing-box-version"
|
||||
make_fixture
|
||||
generate
|
||||
grep -q '"j1": "<b 0x0102030405>"' "$WORK_DIR/singbox_awg.json" || fail "extended 2.6.0 must keep amnezia.j1 with the tag template"
|
||||
grep -q '"itime": 30' "$WORK_DIR/singbox_awg.json" || fail "extended 2.6.0 must keep amnezia.itime"
|
||||
|
||||
# ---- 3. Classic plain-hex i1 payloads are converted to the tag-chain format
|
||||
# for every extended/lx build (userspace WireGuard ignores bare hex).
|
||||
printf '1.13.18-extended-2.6.5\n' >"$WORK_DIR/sing-box-version"
|
||||
cat >"$WORK_DIR/awg_fixture.json" <<JSON
|
||||
{
|
||||
"settings": {
|
||||
".name": "settings",
|
||||
".type": "settings",
|
||||
"enabled": "1"
|
||||
},
|
||||
"section": [
|
||||
{
|
||||
".name": "my_awg",
|
||||
".type": "section",
|
||||
"enabled": "1",
|
||||
"action": "awg",
|
||||
"awg_private_key": "aW5ib3VuZF9wcml2YXRlX2tleQ==",
|
||||
"awg_peer_public_key": "cGVlcl9wdWJsaWNfa2V5",
|
||||
"awg_server_address": "192.168.1.100",
|
||||
"awg_server_port": "51820",
|
||||
"awg_local_address": ["10.0.0.1/24"],
|
||||
"awg_i1": "494e56495445207369703a626f62",
|
||||
"awg_i2": "<b 0x0102><r 12>",
|
||||
"awg_i3": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
JSON
|
||||
generate
|
||||
grep -q '"i1": "<b 0x494e56495445207369703a626f62>"' "$WORK_DIR/singbox_awg.json" ||
|
||||
fail "plain-hex awg_i1 must be wrapped into a <b 0x..> tag"
|
||||
grep -q '"i2": "<b 0x0102><r 12>"' "$WORK_DIR/singbox_awg.json" ||
|
||||
fail "existing tag chains must pass through verbatim"
|
||||
grep -q '"i3"' "$WORK_DIR/singbox_awg.json" && fail "empty cps options must stay absent"
|
||||
|
||||
printf 'AWG variant schema checks passed\n'
|
||||
@@ -201,7 +201,8 @@ echo "=== Normalizing line endings for test scripts ==="
|
||||
|
||||
echo "=== Running unit and integration tests inside OpenWrt container ==="
|
||||
for f in tests/*.sh; do
|
||||
if [ "$f" != "tests/docker_e2e_test.sh" ] && [ "$f" != "tests/container_entrypoint.sh" ]; then
|
||||
if [ "$f" != "tests/docker_e2e_test.sh" ] && [ "$f" != "tests/container_entrypoint.sh" ] &&
|
||||
[ "$f" != "tests/run_all.sh" ] && [ "$f" != "tests/ucode_syntax_lint.sh" ]; then
|
||||
echo "Running $f inside container..."
|
||||
"$DOCKER_BIN" exec -w /work -e SB_REQUIRED_VERSION="1.11.0" "$CONTAINER_NAME" bash "$f"
|
||||
fi
|
||||
|
||||
@@ -88,7 +88,8 @@ fi
|
||||
|
||||
cmp -s "$WORK_DIR/config" "$WORK_DIR/default-config" ||
|
||||
fail "reset must restore the config file to factory defaults"
|
||||
[ "$(stat -c %a "$WORK_DIR/config")" = "600" ] ||
|
||||
# busybox-based images may lack stat(1); ucode's fs module is always present.
|
||||
[ "$(ucode -e "let fs = require('fs'); printf('%o', fs.stat('$WORK_DIR/config').mode & 511);")" = "600" ] ||
|
||||
fail "reset config must be chmod 600"
|
||||
|
||||
[ ! -e "$WORK_DIR/persistent/subscription-cache" ] ||
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
TACHYON_LIB="$ROOT_DIR/tachyon/files/usr/lib"
|
||||
WORK_DIR="$(mktemp -d)"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$WORK_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Canonical serialized secret for key 367a189aee18fa31c190054efd4a8e95 with
|
||||
# faketls host google.com.
|
||||
CANONICAL='ee367a189aee18fa31c190054efd4a8e95676f6f676c652e636f6d'
|
||||
|
||||
fixture_with_secret() {
|
||||
cat >"$WORK_DIR/mtproto_fixture.json" <<JSON
|
||||
{
|
||||
"settings": {
|
||||
".name": "settings",
|
||||
".type": "settings",
|
||||
"enabled": "1"
|
||||
},
|
||||
"server": [
|
||||
{
|
||||
".name": "my_mtproto_server",
|
||||
".type": "server",
|
||||
"enabled": "1",
|
||||
"protocol": "mtproto",
|
||||
"listen_port": "443",
|
||||
"mtproto_secret": "$1",
|
||||
"mtproto_faketls": "google.com"
|
||||
}
|
||||
],
|
||||
"section": [
|
||||
{
|
||||
".name": "default_direct",
|
||||
".type": "section",
|
||||
"enabled": "1",
|
||||
"action": "bypass"
|
||||
}
|
||||
]
|
||||
}
|
||||
JSON
|
||||
}
|
||||
|
||||
generate() {
|
||||
mkdir -p "$WORK_DIR/out.section-cache"
|
||||
ucode -L "$TACHYON_LIB" "$TACHYON_LIB/singbox/generator.uc" generate-config-fixture \
|
||||
"$WORK_DIR/mtproto_fixture.json" "$WORK_DIR/singbox_mtproto.json" "127.0.0.1" 0 0
|
||||
}
|
||||
|
||||
assert_canonical_user_password() {
|
||||
grep -q '"password": "'"$CANONICAL"'"' "$WORK_DIR/singbox_mtproto.json" ||
|
||||
fail "sing-box config must carry the canonical serialized secret as user password"
|
||||
}
|
||||
|
||||
# 1. Full ee-secret in hex passes through unchanged.
|
||||
fixture_with_secret "$CANONICAL"
|
||||
generate
|
||||
assert_canonical_user_password
|
||||
|
||||
# 2. Bare 16-byte key in hex gets wrapped with the marker and faketls host.
|
||||
fixture_with_secret '367a189aee18fa31c190054efd4a8e95'
|
||||
generate
|
||||
assert_canonical_user_password
|
||||
|
||||
# 3. Full secret in padded standard base64 is decoded and re-encoded as hex.
|
||||
fixture_with_secret '7jZ6GJruGPoxwZAFTv1KjpVnb29nbGUuY29t'
|
||||
generate
|
||||
assert_canonical_user_password
|
||||
|
||||
# 4. Bare key in padded standard base64 gets wrapped.
|
||||
fixture_with_secret 'NnoYmu4Y+jHBkAVO/UqOlQ=='
|
||||
generate
|
||||
assert_canonical_user_password
|
||||
|
||||
# 5. Full secret in raw url-safe base64 (as produced by mtg) is normalized.
|
||||
fixture_with_secret '7jZ6GJruGPoxwZAFTv1KjpVnb29nbGUuY29t'
|
||||
generate
|
||||
assert_canonical_user_password
|
||||
|
||||
# 6. Url-safe alphabet (- and _ instead of + and /) is translated.
|
||||
fixture_with_secret 'NnoYmu4Y-jHBkAVO_UqOlQ'
|
||||
generate
|
||||
assert_canonical_user_password
|
||||
|
||||
# 7. The validator accepts a canonical secret and rejects garbage with a
|
||||
# clear message instead of a sing-box FATAL at start time.
|
||||
cat >"$WORK_DIR/validate_secret.uc" <<'UC'
|
||||
let common = require("core.common");
|
||||
let secret = ARGV[1];
|
||||
printf("%s", common.mtproto_secret_canonical(secret, "google.com") == null ? "invalid" : "valid");
|
||||
UC
|
||||
|
||||
[ "$(ucode -L "$TACHYON_LIB" "$WORK_DIR/validate_secret.uc" x "$CANONICAL")" = "valid" ] ||
|
||||
fail "validator helper must accept the canonical hex secret"
|
||||
[ "$(ucode -L "$TACHYON_LIB" "$WORK_DIR/validate_secret.uc" x '7jZ6GJruGPoxwZAFTv1KjpVnb29nbGUuY29t')" = "valid" ] ||
|
||||
fail "validator helper must accept the base64 form of the secret"
|
||||
[ "$(ucode -L "$TACHYON_LIB" "$WORK_DIR/validate_secret.uc" x 'not a real secret')" = "invalid" ] ||
|
||||
fail "validator helper must reject unparseable secrets"
|
||||
[ "$(ucode -L "$TACHYON_LIB" "$WORK_DIR/validate_secret.uc" x '')" = "invalid" ] ||
|
||||
fail "validator helper must reject an empty secret"
|
||||
|
||||
printf 'MTProto server configuration checks passed\n'
|
||||
@@ -88,7 +88,8 @@ SAVED_FILE="$(sed -n 's/.*"file": "\([^"]*\)".*/\1/p' "$WORK_DIR/result.json")"
|
||||
fail "snapshot file name must be <name>-<stamp>.tar.gz: $SAVED_FILE"
|
||||
[ -f "$WORK_DIR/snapshots/$SAVED_FILE" ] ||
|
||||
fail "snapshot archive must exist on disk"
|
||||
[ "$(stat -c %a "$WORK_DIR/snapshots/$SAVED_FILE")" = "600" ] ||
|
||||
# busybox-based images may lack stat(1); ucode's fs module is always present.
|
||||
[ "$(ucode -e "let fs = require('fs'); printf('%o', fs.stat('$WORK_DIR/snapshots/$SAVED_FILE').mode & 511);")" = "600" ] ||
|
||||
fail "snapshot archive must be chmod 600"
|
||||
grep -Fxq 'config/tachyon' <(tar -tzf "$WORK_DIR/snapshots/$SAVED_FILE") ||
|
||||
fail "snapshot must contain config/tachyon member"
|
||||
@@ -136,7 +137,7 @@ grep -Fxq 'restart' "$WORK_DIR/bin.log" ||
|
||||
fail "snapshot-restore must restart the service through the backend entrypoint"
|
||||
grep -Fq "option dns '8.8.8.8'" "$WORK_DIR/config/tachyon" ||
|
||||
fail "snapshot-restore must replace the config with the snapshot content"
|
||||
[ "$(stat -c %a "$WORK_DIR/config/tachyon")" = "600" ] ||
|
||||
[ "$(ucode -e "let fs = require('fs'); printf('%o', fs.stat('$WORK_DIR/config/tachyon').mode & 511);")" = "600" ] ||
|
||||
fail "restored config must be chmod 600"
|
||||
[ "$(cat "$WORK_DIR/tachyon/data")" = 'snap-data' ] ||
|
||||
fail "snapshot-restore must restore the persistent data"
|
||||
|
||||
Reference in New Issue
Block a user