zapret-kvn/xray_fluent/engines/tls_fingerprint.py
loop-uh 70ac787146
Some checks failed
Windows project source guards / test (push) Has been cancelled
Проверки не выдают серверы: рукопожатие Chrome, имя сервера не уходит в открытый DNS
Требование незаметности: ни проверки, ни подключение не должны выдавать
провайдеру (ТСПУ) 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>
2026-10-16 17:57:00 +03:00

55 lines
2.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Отпечаток 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