Когда сервер с конфигом заблокировали — клиент отвалился. Разбираем способ доставки который сломать сложнее чем сам интернет

У любого прокси-сервиса есть слабое место которое не связано с протоколом. Сервер переехал, IP сменился, конфиг устарел — и пользователь сидит без связи пока не получит обновление вручную. Чем больше пользователей, тем острее проблема.

Стандартное решение — раздавать конфиги через HTTPS. Удобно, пока URL не попал в реестр. После этого тысяча человек одновременно пишет в поддержку.

DNS TXT-записи решают эту проблему не через обход блокировок, а через выбор канала который блокировать политически сложно.

Что такое TXT-запись и зачем она вообще существует

TXT-запись — это произвольная строка текста привязанная к домену. Придумали её для SPF (верификация почтовых серверов) и DKIM, потом стали использовать для подтверждения владения доменом в Google Search Console и Let's Encrypt. Технически это просто поле «здесь может лежать любой текст до 255 байт».

Несколько TXT-записей на один домен — нормальная практика. Суммарно можно передать несколько килобайт. VLESS URI с UUID, адресом сервера и параметрами Reality влезает в одну запись с запасом.

Запросить TXT-запись можно так:

dig TXT config.yourdomain.com +short

Или через любой DoH-резолвер:

curl "https://1.1.1.1/dns-query?name=config.yourdomain.com&type=TXT" \
  -H "accept: application/dns-json"

Почему этот канал сложно заблокировать

DNS как мы знаем инфраструктура интернета, и это создаёт определённую защиту. Полная блокировка порта 53 действительно ломает браузер, почту и всё остальное. Но провайдер может действовать тоньше: DNS-спуфинг на уровне провайдера позволяет заблокировать конкретный домен не трогая остальные запросы. Именно так работает большинство DNS-блокировок в РФ. Защита здесь не абсолютная, а в стоимости атаки: заблокировать один домен легко, но если у клиента зашит список из десяти резервных доменов на разных регистраторах — провайдеру нужно блокировать все десять, и делать это оперативно при каждом обновлении списка. DoH дополнительно усложняет задачу: DNS over HTTPS к 1.1.1.1 выглядит как обычный HTTPS-трафик, перехватить его без MITM не получится.

Как это выглядит на практике

Схема минимальной реализации:

1. Создаём TXT-запись с конфигом

Через панель DNS-провайдера добавляем запись:

Тип:  TXT
Имя:  config.yourdomain.com
TTL:  300
Значение: vless://uuid@server:443?type=tcp&security=reality&...

TTL в 300 секунд означает что при смене конфига клиенты подхватят обновление максимум через 5 минут.

2. Клиент запрашивает конфиг при старте

Сначала устанавливаем библиотеку — dns.resolver не входит в стандартную библиотеку Python:

pip install dnspython

pip install httpx
import dns.resolver

def get_config(domain):
    answers = dns.resolver.resolve(domain, 'TXT')
    for rdata in answers:
        for txt_string in rdata.strings:
            return txt_string.decode()

В случае если нужен DoH вместо системного резолвера:

import httpx

def get_config_doh(domain):
    r = httpx.get(
        "https://1.1.1.1/dns-query",
        params={"name": domain, "type": "TXT"},
        headers={"accept": "application/dns-json"}
    )
    answers = r.json().get("Answer", [])
    for answer in answers:
        if answer["type"] == 16:
            return answer["data"].strip('"')

Три строки логики. Клиент при запуске делает один DNS-запрос, получает актуальный URI, подключается.

3. При смене сервера

Меняем только TXT-запись. Никаких обновлений приложения, никаких рассылок пользователям. Через 5 минут все клиенты работают с новым сервером.

Защита конфига от чужих глаз

TXT-запись публична — любой может сделать dig TXT и прочитать конфиг. Для личного использования это некритично: один сервер с одним пользователем не привлекает внимания.

Если сервером пользуются несколько человек и конфиг светить не хочется — два варианта.

Шифрование. Конфиг шифруется симметричным ключом, который передаётся пользователю один раз при онбординге. В TXT лежит зашифрованная строка, клиент расшифровывает локально.

from cryptography.fernet import Fernet

# При создании конфига
key = b'ваш_секретный_ключ_зашитый_в_клиент'
f = Fernet(key)
encrypted = f.encrypt(b'vless://uuid@server:443?...')
# encrypted кладём в TXT

# На клиенте
config = f.decrypt(txt_value).decode()

Двухшаговая доставка. В TXT лежит не сам конфиг, а URL следующего шага — например, ссылка на Pastebin или Github Gist с актуальным конфигом. Первый шаг через DNS, второй через HTTPS. Блокировка одного не убивает канал полностью.

Ограничения о которых нужно знать

Кеширование. DNS кеширует ответы на TTL. Если TTL поставить 3600 — обновление конфига дойдёт до клиента через час. Для оперативных изменений ставить TTL не больше 300, а лучше 60 секунд — если DNS-провайдер позволяет.

Размер. По RFC 1035 (ietf.org/rfc/rfc1035.txt) одна строка внутри TXT-записи ограничена 255 байтами. Но сама запись может содержать несколько строк — приложение собирает их в одну. Теоретический потолок 65 535 байт, практический упирается в UDP MTU: без EDNS0 это 512 байт на весь DNS-пакет, с EDNS0 до 4 КБ. Большинство современных резолверов поддерживают EDNS0, так что в реальности доступно несколько килобайт. Для VLESS URI хватает с запасом, для полного JSON-конфига sing-box с несколькими inbound уже нет.

Домен должен резолвиться. Если сам домен попал в реестр РКН канал собственно мёртв. Решается через несколько резервных доменов зашитых в клиент. Клиент перебирает список по порядку, использует первый который ответил.

FALLBACK_DOMAINS = [
    "config.primary-domain.com",
    "cfg.backup-domain.net",
    "c.reserve-domain.org",
]

def get_config_with_fallback():
    for domain in FALLBACK_DOMAINS:
        try:
            return get_config(domain)
        except Exception:
            continue
    return None

DoH нужно настраивать отдельно. Стандартный dns.resolver в Python ходит через системный резолвер на порту 53. Если нужен DoH — придётся использовать httpx или requests напрямую к API Cloudflare или Google.

Кто уже использует этот подход

Psiphon активно работает с DNS-инфраструктурой — в открытом репозитории psiphon-tunnel-core есть отдельный пакет для DNS и утилиты резолвинга. Конкретный механизм доставки конфигов через TXT-записи публично не задокументирован, поэтому утверждать что они используют именно этот подход не буду. Из открытых реализаций ближе всего к описанной схеме динамические ключи Outline через URL и ряд публичных прокси-сервисов которые хранят актуальный URI в Telegram-боте. DNS TXT в этом контексте — менее популярная но более независимая альтернатива: не требует Telegram, не зависит от мессенджера который сам может быть заблокирован.

Когда это имеет смысл, а когда нет

Канал решает конкретную проблему — доставку и обновление конфига при заблокированном основном канале. Сам по себе он не делает соединение более стойким к детекции и не меняет протокол туннеля.

Если проблема в том что конфиги устаревают и пользователи отваливаются — DNS TXT решает её элегантно и дёшево. Если проблема в том что сам трафик детектируется — нужно смотреть в сторону протокола, а не канала доставки.

Если есть идеи для разбора, нашёл ошибку в конфиге
или хочешь предложить тему — пиши на
aleksandr@murzin.digital Отвечаю.