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>
55 lines
2.4 KiB
Python
55 lines
2.4 KiB
Python
"""Отпечаток TLS-рукопожатия с сервером по умолчанию — как у Chrome.
|
||
|
||
Без ``fp`` в ссылке ядро шлёт ClientHello стандартной библиотеки Go: набор
|
||
шифров и порядок расширений, которых нет ни у одного браузера, поэтому
|
||
оборудование провайдера отличает такое соединение с первого пакета. Значение
|
||
из ссылки (``fp=firefox``, ``randomized`` и т. п.) остаётся как есть; QUIC-путь
|
||
(HTTP/3) uTLS не использует, его не трогаем.
|
||
|
||
Хранимый узел не меняется: отпечаток подставляется только в конфиг ядра.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Any
|
||
|
||
BROWSER_TLS_FINGERPRINT = "chrome"
|
||
|
||
_QUIC_NETWORKS = {"quic", "kcp", "mkcp"}
|
||
|
||
|
||
def tls_over_quic(stream: dict[str, Any]) -> bool:
|
||
"""Рукопожатие идёт по QUIC: uTLS к нему неприменим."""
|
||
|
||
network = str(stream.get("network") or "tcp").lower()
|
||
if network in _QUIC_NETWORKS:
|
||
return True
|
||
for key in ("tlsSettings", "realitySettings"):
|
||
settings = stream.get(key)
|
||
if isinstance(settings, dict):
|
||
alpn = settings.get("alpn")
|
||
if isinstance(alpn, list) and alpn and all(str(item).lower() == "h3" for item in alpn):
|
||
return True
|
||
return False
|
||
|
||
|
||
def apply_browser_fingerprint(outbound: dict[str, Any]) -> dict[str, Any]:
|
||
"""Подставить ``fingerprint: chrome`` в Xray-outbound без своего отпечатка.
|
||
|
||
Меняет переданный словарь (вызывать на копии) и возвращает его же.
|
||
"""
|
||
|
||
stream = outbound.get("streamSettings")
|
||
if not isinstance(stream, dict) or tls_over_quic(stream):
|
||
return outbound
|
||
security = str(stream.get("security") or "").lower()
|
||
key = {"tls": "tlsSettings", "reality": "realitySettings"}.get(security)
|
||
if key is None:
|
||
return outbound
|
||
settings = stream.get(key)
|
||
if not isinstance(settings, dict):
|
||
settings = {}
|
||
stream[key] = settings
|
||
if not str(settings.get("fingerprint") or "").strip():
|
||
settings["fingerprint"] = BROWSER_TLS_FINGERPRINT
|
||
return outbound
|