Технологии

Почему маскировка Hysteria2 не спасает закрытый адрес: замеры UDP (2026)

10 мин чтения

1 октября 2026 года мы отправили из Москвы десять одинаковых UDP-датаграмм — одна и та же строка, порт 443, одни и те же секунды — на два наших сервера у одного хостера в Нидерландах. На первый дошло 5 из 10. На второй — 0 из 10.

В тот же день второму серверу добавили адрес из другой подсети того же хостера. С двух московских точек на него дошло 10 из 10, и QUIC-сервер ответил.

Вывод, к которому мы пришли не сразу: в наших случаях судьбу UDP решал адрес получателя, а не то, как выглядят пакеты. Маскировка трафика — obfs Salamander или Gecko в Hysteria2 — не может спасти адрес, до которого пакеты просто не доезжают. Ниже замеры на наших собственных серверах и команды, чтобы повторить их на своих.

Если вы просто пользуетесь VPN: сервер на UDP (у нас это Нидерланды на Hysteria2) подключается и через секунды или ровно через полминуты замирает — переключитесь на сервер на TCP (VLESS Reality). Искать маскировку в настройках приложения бесполезно: дело в адресе сервера или в том, как ваша сеть обходится с UDP. Остальной текст — для тех, кто держит свои серверы. Общий разбор симптомов — в гайде Happ не подключается.

Как мы сначала ошиблись

24 сентября наш сервер на Hysteria2 (протокол поверх QUIC, то есть UDP) почти перестал принимать подключения из России: за двадцать минут их стало не сотни в час, а единицы. Сам сервер был здоров, из-за рубежа к нему подключались.

25 сентября мы померили. Из двух точек в московских дата-центрах (обе у одного хостера) слали «QUIC-стук» — пакет, на который QUIC-сервер отвечает служебным пакетом. Как он устроен, ниже в разделе «Как повторить».

Замер 25 сентября 2026 года с двух точек в московских дата-центрах
КудаЧемРезультат
Сервер Б, основной адресQUIC-стук, UDP 4431 из 20 и 3 из 20
Сервер Б, второй адрес из другой подсетиQUIC-стук, UDP 4431 из 20 и 1 из 20
Google и CloudflareQUIC-стук, UDP 44320 из 20
Другой европейский серверUDP-эхо0–1 из 10 (из Европы — 8 из 8)
Сервер Б, оба адресаTCP 4430–1 из 15
Сервер А, контрольTCP15 из 15

Мы записали: «из России режут UDP до иностранных хостингов вообще, смена адреса не поможет». Это было неверно. Два адреса одной машины и ещё один сервер — слишком мало, чтобы говорить «вообще». А подсказка лежала в той же таблице: TCP к серверу Б молчал так же, как UDP. Закрыт был адрес целиком, а не протокол.

1 октября: одинаковые пакеты, разная судьба

Опыт поставили так, чтобы различался только получатель. Одна московская точка, одни и те же секунды, одна и та же строка в каждой датаграмме, порт 443, один хостер, одна страна. На обоих серверах пакеты ловил tcpdump.

1 октября 2026 года: 10 одинаковых датаграмм с одной московской точки на UDP 443
ПолучательДошло
Сервер А5 из 10 (у него два адреса, и, судя по всему, проходит один из них)
Сервер Б0 из 10

Содержимое одинаковое, время одинаковое, а итог разный. Значит, решение принимается по адресу назначения.

Дальше серверу Б добавили третий адрес — из другой подсети того же хостера. У нашего хостера это около 2,4 $ в месяц — заметно дешевле переезда.

1 октября 2026 года: тот же сервер Б после добавления адреса из другой подсети
ПолучательДошло с двух московских точек
Сервер Б, старые адреса0
Сервер Б, новый адрес из третьей подсети10 из 10 и 10 из 10

QUIC-стук на новый адрес тоже прошёл: сервер ответил служебным пакетом. Важная мелочь — ответ пришёл с того же адреса, на который мы стучались. На сервере с несколькими адресами это не гарантировано: если ответ уйдёт с другого адреса, клиент или домашний роутер его, скорее всего, отбросят. Наш сервер ответил правильно.

Клиентов перевели на новый адрес; сертификат и сам сервер остались прежними. Вечером 1 октября QUIC-стук давал 10 из 10 с обеих точек, клиенты подключались.

Вид пакетов ни при чём: опыт на домашней линии

Второй опыт — 17 сентября 2026 года, домашняя линия крымского оператора «Волна» (K-Telekom, AS203451), iPhone, Happ 5.8.0. Здесь адрес как раз пропускали: рукопожатие и авторизация проходили. А дальше было вот что.

Мы подняли рядом с рабочим сервером тестовый: тот же сертификат и та же авторизация, но с маскировкой Salamander и на UDP 8443 вместо 443. Пакеты снимали на сервере.

  • В трёх сессиях подряд от телефона пришло 11, 12 и 11 пакетов — все в первые 0,5–0,6 секунды. Это рукопожатие, авторизация и первые запросы.
  • Дальше от телефона 30 секунд не приходило ничего, хотя сервер слал ответы, повторы и пробный пакет на 1374 байта.
  • На 30-й секунде обе стороны закрыли сессию по таймауту простоя. В журнале сервера это выглядит так:
accepting stream failed: timeout: no recent network activity
  • Сессия на рабочий сервер (443, без маскировки, с перескоком портов) умерла точно так же.
  • За полсуток на рабочем сервере 39 обрывов из 40 случились ровно через 30 секунд после подключения. 30 секунд — это maxIdleTimeout сервера, значение по умолчанию в Hysteria2.

Что этим исключено:

  • Опознание QUIC. Случайный шум Salamander умер так же, как обычный QUIC.
  • Порт 443. На 8443 то же самое.
  • MTU. Пакеты по 1288 байт первые полсекунды ходили в обе стороны.
  • «Мёртвая сеть». Рукопожатие завершалось.

Остаётся одно объяснение: в этой сети оператор пропускает первую дюжину пакетов UDP-потока и дальше его глушит. Перескок портов раз в ~30 секунд тут бессилен, когда поток живёт 0,6 секунды. В тот же день у того же человека работал TCP-сервер (VLESS Reality).

Механизм здесь другой, чем в опыте 1 октября: режут не адрес, а поток. Но главное совпадает — вид пакетов ничего не решал.

Сама строка no recent network activity — не улика. Она сыплется и на здоровых серверах: это обычный таймаут простоя. Улика — доля: почти все сессии умирают ровно на 30-й секунде.

Крымом дело не ограничилось. 22 сентября на нашем сервере Hysteria2 стук из Москвы проходил 5 из 5, а живые сессии из России рвались с медианой 30 секунд: 62 обрыва из 62 за полтора часа, все с той же строкой в журнале.

Это не только UDP

Ещё три наблюдения, в которых решал адрес или сеть получателя, а не протокол:

  • 21–22 сентября 2026 года, TCP-сервер. Адрес одного нашего сервера перестал отвечать из Москвы: 0 из 5 с обеих точек. Новый адрес у того же хостера и в той же автономной системе — 5 из 5 из Москвы, и из Крыма он тоже заработал. Два адреса, одна сеть, разная судьба: решал именно адрес.
  • 4 сентября 2026 года, Крым. До одной сети поток любого вида — TCP 443, TCP 2096 и QUIC — глох на 12–17 КБ. Сервер в другой сети в ту же минуту отдал тому же человеку больше 240 КБ.
  • Cloudflare, 26 июня 2025 года. В своём блоге Cloudflare написал: с 9 июня 2025 года российские провайдеры пропускают к его адресам только первые 16 КБ, одинаково по TCP и QUIC.

А массовый сбой больше 20 VPN-сервисов 4 августа 2026 года iXBT со ссылкой на Telegram-канал «Эксплойт» объяснял блокировкой целых подсетей крупных хостингов. Это пересказ, первоисточника мы не видели.

Что это значит для маскировки

Документация Hysteria сама говорит, зачем нужна obfs: для сетей, которые режут именно QUIC или HTTP/3, но не UDP вообще. Salamander превращает каждый пакет в случайные байты. Gecko (в сервере с версии 2.9.2, пока помечен как экспериментальный) вдобавок дробит пакеты рукопожатия на куски случайного размера. Ни то ни другое не поможет, если датаграмму выбрасывают по адресу ещё до сервера.

Две ловушки, если всё же включать:

  1. 1Пароль obfs одинаковый на сервере и клиенте. Включить его на действующем входе — значит отключить всех клиентов без пароля. Только вторым слушателем на другом порту.
  2. 2С obfs сервер перестаёт быть настоящим HTTP/3-сервером (так написано в документации). Маскировка под обычный сайт на этом входе пропадает.

Gecko, кроме того, понимает не каждый клиент: проверьте ваше приложение до включения. Где obfs, возможно, имеет смысл — на адресе, до которого UDP доходит: чтобы его дольше не опознавали. Это гипотеза, мы её не мерили.

Как повторить

Нужны сервер за рубежом с root, точка в России (VPS или домашний компьютер) и второй сервер для контроля. В командах ниже адреса — заглушки, подставьте свои.

1. На сервере — ловим датаграммы от вашей точки. 203.0.113.7 здесь — публичный адрес точки в России.

sudo tcpdump -ni any -c 50 'udp and dst port 443 and src host 203.0.113.7'

2. С точки в России — одинаковые датаграммы на два сервера в одни и те же секунды.

python3 - <<'EOF'
import socket, time
targets = [("SERVER_A", 443), ("SERVER_B", 443)]  # your two servers
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for i in range(10):
    for t in targets:
        s.sendto(b"udp-reach-%02d" % i, t)
    time.sleep(0.5)
EOF

Считайте, сколько строк udp-reach-NN увидел tcpdump на каждом сервере.

3. QUIC-стук: проверка полного круга без клиента. Скрипт шлёт пакет с зарезервированной версией QUIC 0x0a0a0a0a, дополненный до 1200 байт. По RFC 9000 сервер на неизвестную версию должен (SHOULD) ответить пакетом Version Negotiation, а датаграммы меньше 1200 байт с неизвестной версией обязан выбросить. Ответ доказывает, что ваш пакет дошёл и ответ вернулся, и показывает, с какого адреса он пришёл. Работает с Hysteria2 без obfs и с большинством QUIC-серверов. Оговорка: стандарт разрешает серверу ограничивать число таких ответов, поэтому молчание на стук — повод снять tcpdump на сервере (шаг 1), а не окончательный вывод.

#!/usr/bin/env python3
# QUIC knock: reserved version 0x0a0a0a0a -> the server answers with Version Negotiation.
# usage: python3 quic_knock.py HOST [PORT] [TRIES]
import os, socket, struct, sys, time

if len(sys.argv) < 2:
    sys.exit("usage: python3 quic_knock.py HOST [PORT] [TRIES]")
host = sys.argv[1]
port = int(sys.argv[2]) if len(sys.argv) > 2 else 443
tries = int(sys.argv[3]) if len(sys.argv) > 3 else 10
fam, _, _, _, addr = socket.getaddrinfo(host, port, type=socket.SOCK_DGRAM)[0]
ok = 0
for n in range(1, tries + 1):
    dcid, scid = os.urandom(8), os.urandom(8)
    pkt = b"\xc0" + struct.pack("!I", 0x0A0A0A0A) + b"\x08" + dcid + b"\x08" + scid
    pkt += b"\x00" * (1200 - len(pkt))  # servers drop datagrams under 1200 bytes
    with socket.socket(fam, socket.SOCK_DGRAM) as s:
        s.settimeout(2)
        t0 = time.monotonic()
        s.sendto(pkt, addr)
        try:
            data, src = s.recvfrom(2048)
        except socket.timeout:
            print(f"{n:2}: no reply"); continue
        except OSError as e:  # e.g. ICMP "port unreachable" reported by the OS
            print(f"{n:2}: {e}"); continue
    rtt = (time.monotonic() - t0) * 1000
    # Version Negotiation: version field 0, our SCID echoed back as its DCID
    good = len(data) > 6 and data[1:5] == b"\0\0\0\0" and data[6:6 + data[5]] == scid
    ok += good
    print(f"{n:2}: {'VN' if good else 'odd reply'} from {src[0]} in {rtt:.0f} ms")
    time.sleep(0.3)
print(f"{ok} of {tries} replied")

Запуск: python3 quic_knock.py SERVER_B 443 20. Мы проверили скрипт 1 октября на публичных серверах: www.google.com и cloudflare.com отвечают, github.com (QUIC не держит) молчит, как и должен.

4. TCP к тому же адресу. Если молчит и TCP, адрес закрыт целиком.

nc -vz -w 5 SERVER_B 443

«succeeded» или «refused» — пакеты до адреса доходят. «timed out» — режут, если на самом сервере нет файрвола, который молча отбрасывает пакеты; это отсекает контроль из-за рубежа (шаг 5). На macOS вместо -w 5 используйте -G 5.

5. Контроль — обязательно. В те же минуты стучитесь в заведомо живую цель (у нас Google и Cloudflare давали 20 из 20) и в тот же сервер из точки вне России. Без контроля не отличить закрытый адрес от проблемы вашей сети.

Как читать результат проверки
Что видитеЧто это значит
До сервера ноль датаграмм, до контроля доходитадрес или подсеть закрыты для этой сети; протокол и маскировка не помогут
Доходит стабильная часть, например половиначасть адресов или маршрутов фильтруется; смотрите по адресам отдельно
Стук отвечает, а сессия умирает через секундырежут поток после первых пакетов; переходите на TCP
Не доходит и до контролядело в вашей сети или в UDP на ней вообще

Ограничения

  • Дата. Замеры с новым адресом — от 1 октября 2026 года, и описывают они тот день. В тот же день новый адрес на несколько часов пропал с сетевого интерфейса самого сервера: это был сбой на нашей стороне, а не блокировка. Адрес вернули, и стук снова дал 10 из 10.
  • Точки замера. Две точки в московских дата-центрах у одного хостера плюс одна домашняя линия в Крыму. Домашние сети других регионов могут вести себя иначе.
  • Выборки маленькие: 10–20 пакетов на опыт.
  • Строка «UDP-эхо» из таблицы 25 сентября не разобрана. Тот сервер мы отдельно не проверяли, TCP к нему в тот день не мерили. Ещё 14 сентября UDP с одной из тех же московских точек не доходил до нескольких наших европейских серверов. Возможно, и тот адрес был закрыт, но доказательством ни в одну сторону эту строку мы не считаем.
  • «Подсеть» — гипотеза. Новый адрес из другой подсети спасал дважды (4 августа и 1 октября) и один раз пришёл уже закрытым (25 сентября). Это лотерея: мерьте новый адрес до того, как переводить на него людей.

Что делать, если держите свой сервер

  1. 1Прежде чем менять протокол или включать маскировку, проверьте, доходят ли пакеты вообще: шаги 1–3 выше.
  2. 2Локация «умерла» из России — сначала попросите у того же хостера адрес из другой подсети и померьте его. Переезд и смена протокола — потом.
  3. 3Держите рядом с UDP-входом TCP-вход. Где режут поток после первых пакетов, нас спасал только TCP.

Чем VLESS Reality поверх TCP отличается от Hysteria2 поверх UDP в обычной работе, разобрано в гайде VLESS Reality или Hysteria2. Как выглядит со стороны пользователя обрыв после первых килобайт — в гайде VPN подключается и через секунды замирает.

Поможет ли obfs Salamander или Gecko, если UDP до сервера не доходит?

Нет. Маскировка меняет вид пакетов, а в наших замерах их отбрасывали по адресу назначения ещё до сервера: одинаковые датаграммы в одни и те же секунды дошли до одного сервера и не дошли до другого. Сначала проверьте, доходят ли пакеты, и попробуйте адрес из другой подсети.

Почему сессия Hysteria2 рвётся ровно через 30 секунд?

30 секунд — это maxIdleTimeout, таймаут простоя Hysteria2 по умолчанию. Если сеть пропускает только первые пакеты потока, дальше от клиента ничего не приходит, и сервер закрывает сессию ровно на 30-й секунде. Сама строка no recent network activity в журнале бывает и на здоровых серверах; улика — когда так умирают почти все сессии.

Что делать обычному пользователю, если UDP-сервер замирает?

Переключиться на сервер на TCP (VLESS Reality). Искать маскировку в настройках приложения бесполезно: дело в адресе сервера или в том, как сеть обходится с UDP, и решается это не в приложении.

Помогла статья?

Ответ ни к чему не обязывает и виден только нам.

Переслать статью

Пригодится тому, у кого та же проблема.

TelegramWhatsAppVK