Технологии
Почему маскировка Hysteria2 не спасает закрытый адрес: замеры UDP (2026)
1 октября 2026 года мы отправили из Москвы десять одинаковых UDP-датаграмм — одна и та же строка, порт 443, одни и те же секунды — на два наших сервера у одного хостера в Нидерландах. На первый дошло 5 из 10. На второй — 0 из 10.
В тот же день второму серверу добавили адрес из другой подсети того же хостера. С двух московских точек на него дошло 10 из 10, и QUIC-сервер ответил.
Вывод, к которому мы пришли не сразу: в наших случаях судьбу UDP решал адрес получателя, а не то, как выглядят пакеты. Маскировка трафика — obfs Salamander или Gecko в Hysteria2 — не может спасти адрес, до которого пакеты просто не доезжают. Ниже замеры на наших собственных серверах и команды, чтобы повторить их на своих.
Как мы сначала ошиблись
24 сентября наш сервер на Hysteria2 (протокол поверх QUIC, то есть UDP) почти перестал принимать подключения из России: за двадцать минут их стало не сотни в час, а единицы. Сам сервер был здоров, из-за рубежа к нему подключались.
25 сентября мы померили. Из двух точек в московских дата-центрах (обе у одного хостера) слали «QUIC-стук» — пакет, на который QUIC-сервер отвечает служебным пакетом. Как он устроен, ниже в разделе «Как повторить».
| Куда | Чем | Результат |
|---|---|---|
| Сервер Б, основной адрес | QUIC-стук, UDP 443 | 1 из 20 и 3 из 20 |
| Сервер Б, второй адрес из другой подсети | QUIC-стук, UDP 443 | 1 из 20 и 1 из 20 |
| Google и Cloudflare | QUIC-стук, UDP 443 | 20 из 20 |
| Другой европейский сервер | UDP-эхо | 0–1 из 10 (из Европы — 8 из 8) |
| Сервер Б, оба адреса | TCP 443 | 0–1 из 15 |
| Сервер А, контроль | TCP | 15 из 15 |
Мы записали: «из России режут UDP до иностранных хостингов вообще, смена адреса не поможет». Это было неверно. Два адреса одной машины и ещё один сервер — слишком мало, чтобы говорить «вообще». А подсказка лежала в той же таблице: TCP к серверу Б молчал так же, как UDP. Закрыт был адрес целиком, а не протокол.
1 октября: одинаковые пакеты, разная судьба
Опыт поставили так, чтобы различался только получатель. Одна московская точка, одни и те же секунды, одна и та же строка в каждой датаграмме, порт 443, один хостер, одна страна. На обоих серверах пакеты ловил tcpdump.
| Получатель | Дошло |
|---|---|
| Сервер А | 5 из 10 (у него два адреса, и, судя по всему, проходит один из них) |
| Сервер Б | 0 из 10 |
Содержимое одинаковое, время одинаковое, а итог разный. Значит, решение принимается по адресу назначения.
Дальше серверу Б добавили третий адрес — из другой подсети того же хостера. У нашего хостера это около 2,4 $ в месяц — заметно дешевле переезда.
| Получатель | Дошло с двух московских точек |
|---|---|
| Сервер Б, старые адреса | 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Пароль obfs одинаковый на сервере и клиенте. Включить его на действующем входе — значит отключить всех клиентов без пароля. Только вторым слушателем на другом порту.
- 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–3 выше.
- 2Локация «умерла» из России — сначала попросите у того же хостера адрес из другой подсети и померьте его. Переезд и смена протокола — потом.
- 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, и решается это не в приложении.
Помогла статья?
Ответ ни к чему не обязывает и виден только нам.