refactor(xray-proxy): срезана accidental-сложность CLI, тестов и спеки

- Зачем:
  - CLI и спека были переусложнены относительно принятой модели угроз
    (один пользователь, доверенная машина): лишняя ось версий Xray и
    защита от входов, которые не возникают при ручном редактировании.
- Что:
  - убрана поддержка Xray 25.12.8 — поддерживается только 26.3.27;
    allowInsecure валидируется, но никогда не эмитится в JSON.
  - удалена проверка владения сокетом через /proc/net/tcp; идентичность
    процесса и сверка адрес/порт со state сохранены.
  - валидация INI/профиля/адреса/query сведена к отказам на реальные
    опечатки; scoped IPv6 отклоняется одинаково на Python 3.8 и 3.9+.
  - удалены fsync/O_EXCL-церемония, SIGALRM-таймер, zombie-инспекция,
    мёртвый отпечаток профиля и двойной разбор extra/alpn.
  - shell.bash принимает вывод env без ре-валидации, сохранив атомарность;
    спека и issue 01–05 приведены к новому объёму (тикет 06, леджер).
- Проверка:
  - python3 -m unittest discover -s tests — 34 теста OK.
  - bash tests/test_proxy_launchers.sh — 8 проверок PASS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
ddadminandClaude Fable 5 committed 2026-07-20 04:13:54 -04:00
1 parent 71e16a795a
commit 98bfd6338a
12 files changed
+384 -508

No files matched your search

+36 -66
View File
@@ -4,7 +4,6 @@ import os
import subprocess
import sys
import tempfile
import time
import unittest
from pathlib import Path
@@ -13,7 +12,7 @@ REPO_ROOT = Path(__file__).resolve().parents[1]
CLI = REPO_ROOT / "xray-proxy/.local/bin/xray-proxy"
def checking_socket_setup(outcome="success", close_delay=0.0):
def checking_socket_setup(outcome="success"):
if outcome == "success":
outcome_line = " return Connection()\n"
elif outcome == "refused":
@@ -21,13 +20,11 @@ def checking_socket_setup(outcome="success", close_delay=0.0):
else:
raise ValueError("unknown socket outcome: {}".format(outcome))
return (
"import json, os, socket, time\n"
"import json, os, socket\n"
"expected_connection = json.loads(os.environ['TEST_EXPECTED_CONNECTION'])\n"
"class Connection:\n"
" def close(self):\n"
" time.sleep("
+ repr(close_delay)
+ ")\n"
" pass\n"
"def checked_create_connection(endpoint, timeout):\n"
" actual_connection = [endpoint[0], endpoint[1], timeout]\n"
" if actual_connection != expected_connection:\n"
@@ -226,36 +223,27 @@ class XrayProxyCliTest(unittest.TestCase):
self.assertIn("HTTP_PROXY=http://[2001:db8::1]:23456\n", result.stdout)
self.assertIn("ALL_PROXY=socks5://[2001:db8::1]:23456\n", result.stdout)
def test_preflight_timeout_bounds_delayed_dns_resolution(self):
self.write_config(12334, address="slow-dns.example")
def test_scoped_ipv6_is_rejected_when_ipaddress_lacks_scope_support(self):
self.write_config(23456, address="fe80::1%eth0", no_proxy_extra="")
setup = (
"import socket, time; "
"socket.getaddrinfo = lambda *args, **kwargs: time.sleep(5)"
"import ipaddress\n"
"real_ip_address = ipaddress.ip_address\n"
"def python38_ip_address(value):\n"
" if '%' in value:\n"
" raise ValueError('scope IDs unsupported')\n"
" return real_ip_address(value)\n"
"ipaddress.ip_address = python38_ip_address\n"
+ SUCCESSFUL_SOCKET_SETUP
)
started = time.monotonic()
result = self.run_cli_with_socket_setup(
("status",), setup, {"PROXY_PREFLIGHT_TIMEOUT": "0.1"}
)
elapsed = time.monotonic() - started
self.assertEqual(1, result.returncode)
self.assertLess(elapsed, 1.0)
self.assertIn("таймаут", result.stderr)
self.assertNotIn("Traceback", result.stderr)
def test_preflight_timer_is_cancelled_before_closing_successful_connection(self):
self.write_config(23456)
setup = checking_socket_setup(close_delay=0.1)
result = self.run_cli_with_socket_setup(
("status",),
("env",),
setup,
{"PROXY_PREFLIGHT_TIMEOUT": "0.01"},
expected_connection=("127.0.0.1", 23456, 0.01),
expected_connection=("fe80::1%eth0", 23456, 2.0),
)
self.assertEqual(0, result.returncode, result.stderr)
self.assertIn("доступен", result.stdout)
self.assertNotIn("Traceback", result.stderr)
self.assertNotEqual(0, result.returncode)
self.assertEqual("", result.stdout)
self.assertIn("address", result.stderr)
def test_unreachable_env_prints_no_partial_output(self):
self.write_config(23456)
@@ -371,30 +359,13 @@ class XrayProxyCliTest(unittest.TestCase):
self.assertNotEqual(0, result.returncode)
self.assertIn("run -- COMMAND", result.stderr)
def test_invalid_preflight_timeout_is_rejected_by_status_env_and_run(self):
def test_invalid_preflight_timeout_is_rejected_clearly(self):
self.write_config(12334)
for value in ("", "zero", "0", "-1", "nan", "inf"):
for command in (("status",), ("env",), ("run", "--", "true")):
with self.subTest(value=value, command=command):
result = self.run_cli(
*command, environment={"PROXY_PREFLIGHT_TIMEOUT": value}
)
self.assertNotEqual(0, result.returncode)
self.assertIn("положительным числом", result.stderr)
if command == ("env",):
self.assertEqual("", result.stdout)
def test_extreme_positive_timeouts_fail_without_traceback(self):
self.write_config(12334)
for value in ("1e-7", "1e308"):
with self.subTest(value=value):
result = self.run_cli(
"status", environment={"PROXY_PREFLIGHT_TIMEOUT": value}
)
self.assertNotEqual(0, result.returncode)
self.assertEqual("", result.stdout)
self.assertIn("Ошибка:", result.stderr)
self.assertNotIn("Traceback", result.stderr)
result = self.run_cli(
"status", environment={"PROXY_PREFLIGHT_TIMEOUT": "not-a-number"}
)
self.assertNotEqual(0, result.returncode)
self.assertIn("положительным числом", result.stderr)
def test_ini_rejects_structure_key_and_value_grammar_errors(self):
valid = (
@@ -407,13 +378,9 @@ class XrayProxyCliTest(unittest.TestCase):
)
invalid_configs = {
"unknown section": valid + "[other]\nkey = value\n",
"default section": "[DEFAULT]\nfoo = bar\n" + valid,
"unknown key": valid + "extra = value\n",
"wrong key case": valid.replace("type = external", "Type = external"),
"duplicate key": valid + "port = 12335\n",
"missing key": valid.replace("name = Test\n", ""),
"multiline value": valid.replace("name = Test", "name = Test\n continued"),
"nul value": valid.replace("name = Test", "name = Te\0st").encode("utf-8"),
"empty name": valid.replace("name = Test", "name = "),
"empty address": valid.replace("address = 127.0.0.1", "address = "),
"port zero": valid.replace("port = 12334", "port = 0"),
@@ -431,8 +398,17 @@ class XrayProxyCliTest(unittest.TestCase):
self.assertEqual("", result.stdout)
self.assertIn("Ошибка:", result.stderr)
def test_address_accepts_only_ip_literals_or_ascii_dns_names(self):
valid_addresses = ("127.0.0.1", "2001:db8::1", "proxy-1.example")
def test_address_accepts_ip_literals_and_simple_ascii_names(self):
valid_addresses = (
"127.0.0.1",
"2001:db8::1",
"proxy-1.example",
".leading.example",
"trailing.example.",
"bad..example",
"-bad.example",
"bad-.example",
)
for address in valid_addresses:
with self.subTest(address=address):
self.write_config(12334, address=address)
@@ -441,18 +417,12 @@ class XrayProxyCliTest(unittest.TestCase):
invalid_addresses = (
"http://proxy.example",
"proxy.example:12334",
"[::1]",
"fe80::1%eth0",
"has space",
"proxy/path",
"user@proxy",
".leading.example",
"trailing.example.",
"bad..example",
"-bad.example",
"bad-.example",
"прокси.example",
"fe80::1%eth0",
)
for address in invalid_addresses:
with self.subTest(address=address):