Some checks failed
Windows project source guards / test (push) Has been cancelled
Требование незаметности: ни проверки, ни подключение не должны выдавать провайдеру (ТСПУ) VPN-серверы. - Без fp в ссылке рукопожатие с сервером — uTLS chrome, а не ClientHello библиотеки Go, который отличим с первого пакета. Значение из ссылки сохраняется, QUIC (HTTP/3, mKCP) не трогаем, хранимый узел не меняется — отпечаток подставляется во все конфиги ядра (sing-box, Xray, пул, замер). - Фоновое обновление адреса больше не спрашивает системный резолвер про имя сервера при каждом подключении. Подмена DNS выводится только из запроса, который всё равно понадобился (DoH недоступен, провайдер говорит «имени нет», а кеш его знает). - После подключения имена всех серверов пула больше не резолвятся (DoH к пяти резолверам на каждое имя) ради маскировки журнала: регистрируются только адреса из кеша; цель горячего переключения резолвится перед ним. - Подписка по http:// к адресу в интернете грузится только через туннель: напрямую провайдер увидел бы весь список серверов; редирект на http:// на прямом пути отклоняется. Локальная сеть — как раньше. - Проверка связи — браузерный User-Agent вместо ZapretKVN/0.4. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
65 lines
3.3 KiB
Python
65 lines
3.3 KiB
Python
from __future__ import annotations
|
||
|
||
import unittest
|
||
|
||
from xray_fluent.engines.singbox.config_builder import build_singbox_outbound
|
||
from xray_fluent.engines.tls_fingerprint import BROWSER_TLS_FINGERPRINT, apply_browser_fingerprint
|
||
from xray_fluent.engines.xray.config_builder import build_xray_config
|
||
from xray_fluent.importer.link_parser import parse_single
|
||
from xray_fluent.network.speed_test_worker import build_speed_test_config
|
||
from xray_fluent.profiles.models import AppSettings, RoutingSettings
|
||
|
||
UUID = "11111111-2222-3333-4444-555555555555"
|
||
REALITY = (
|
||
f"vless://{UUID}@vpn.example.com:443?security=reality&sni=www.example.com"
|
||
"&pbk=Z84J2IelR9ch3k8VtlVhhs5ycBUlXA7wHBWcBrjqnAw&sid=ab&type=tcp#r"
|
||
)
|
||
|
||
|
||
def _proxy(config: dict) -> dict:
|
||
return next(item for item in config["outbounds"] if item.get("tag") == "proxy")
|
||
|
||
|
||
class SingBoxFingerprintTests(unittest.TestCase):
|
||
"""Без fp в ссылке рукопожатие с сервером — как у Chrome, а не ClientHello Go."""
|
||
|
||
def test_trojan_without_fp_gets_chrome(self) -> None:
|
||
node = parse_single("trojan://secret@vpn.example.com:443?sni=vpn.example.com#t")
|
||
tls = build_singbox_outbound(node)["tls"]
|
||
self.assertEqual(tls["utls"], {"enabled": True, "fingerprint": BROWSER_TLS_FINGERPRINT})
|
||
|
||
def test_explicit_fp_is_kept(self) -> None:
|
||
node = parse_single("trojan://secret@vpn.example.com:443?sni=vpn.example.com&fp=firefox#t")
|
||
self.assertEqual(build_singbox_outbound(node)["tls"]["utls"]["fingerprint"], "firefox")
|
||
|
||
def test_plain_tcp_without_tls_is_untouched(self) -> None:
|
||
node = parse_single("ss://YWVzLTI1Ni1nY206c2VjcmV0@vpn.example.com:8388#s")
|
||
self.assertNotIn("tls", build_singbox_outbound(node))
|
||
|
||
|
||
class XrayFingerprintTests(unittest.TestCase):
|
||
def test_reality_without_fp_gets_chrome_in_runtime_config(self) -> None:
|
||
node = parse_single(REALITY)
|
||
self.assertNotIn("fingerprint", node.outbound["streamSettings"]["realitySettings"])
|
||
proxy = _proxy(build_xray_config(node, RoutingSettings(), AppSettings()))
|
||
self.assertEqual(proxy["streamSettings"]["realitySettings"]["fingerprint"], BROWSER_TLS_FINGERPRINT)
|
||
# Хранимый узел не меняется: отпечаток подставляется только в конфиг ядра.
|
||
self.assertNotIn("fingerprint", node.outbound["streamSettings"]["realitySettings"])
|
||
|
||
def test_speed_test_core_uses_the_same_handshake(self) -> None:
|
||
proxy = _proxy(build_speed_test_config(parse_single(REALITY), 18080))
|
||
self.assertEqual(proxy["streamSettings"]["realitySettings"]["fingerprint"], BROWSER_TLS_FINGERPRINT)
|
||
|
||
def test_quic_transport_is_left_alone(self) -> None:
|
||
outbound = {"streamSettings": {"network": "xhttp", "security": "tls", "tlsSettings": {"alpn": ["h3"]}}}
|
||
apply_browser_fingerprint(outbound)
|
||
self.assertNotIn("fingerprint", outbound["streamSettings"]["tlsSettings"])
|
||
|
||
def test_explicit_fingerprint_is_kept(self) -> None:
|
||
outbound = {"streamSettings": {"security": "tls", "tlsSettings": {"fingerprint": "safari"}}}
|
||
apply_browser_fingerprint(outbound)
|
||
self.assertEqual(outbound["streamSettings"]["tlsSettings"]["fingerprint"], "safari")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
unittest.main()
|