Discord через IPv6 прокси: как обойти блокировку голосовых каналов и не потерять RTC-соединение
Содержание
- Почему голос Discord не идёт через обычный HTTP-прокси
- Что именно резолвит Discord при заходе в голосовой канал
- Схема: HTTP API через прокси, RTC через IPv6
- Настройка прокси-клиента: что завернуть, а что пустить напрямую
- Проверка ICE-кандидатов: где ломается RTC
- STUN Binding Request, RFC 5389
- Кейс: nginx как TCP-релей для gateway, голос — мимо
- Кейс: SOCKS5 с UDP ASSOCIATE и почему он не спасает
- IPv6-туннель, если провайдер не даёт нативный
- DNS: где чаще всего теряется IPv6-путь
- Что делать с relay-кандидатами
- Итоговая конфигурация и метрики
Discord — это не один сервис. Это зоопарк из HTTP API, WebSocket-шлюза, CDN для вложений и отдельной RTC-инфраструктуры на UDP. Блокируют их по-разному, и проксирование «всё через один SOCKS5» разваливается на голосовых каналах. Разберём, где именно рвётся и как это чинить.
Почему голос Discord не идёт через обычный HTTP-прокси
Голосовой канал Discord работает поверх WebRTC: SRTP для медиа, RTP/RTCP для контроля, ICE для пробивания NAT. Всё это UDP. HTTP-прокси (CONNECT) умеет только TCP — он просто не увидит голосовой трафик. SOCKS5 умеет UDP ASSOCIATE, но большинство клиентов его не запрашивают, а многие прокси-серверы попросту отключают UDP-релей.
Дальше — ICE. Клиент собирает кандидатов: локальные адреса, STUN-reflexive, TURN-relay. Если вы сидите за прокси, локальные кандидаты бесполезны (они указывают на интерфейс, который Discord не видит), а STUN-запросы через прокси часто не проходят. Остаётся TURN — но и его адрес надо резолвить и достучаться.
В итоге: текст и звук в «лобби» работают, а при заходе в канал Discord выдаёт «RTC Connecting» и через 20 секунд — «No Route». Классика.
Что именно резолвит Discord при заходе в голосовой канал
Последовательность такая. Клиент дёргает `GET /voice/regions`, потом `POST /voice/.../join` через HTTP API. В ответ приходит `endpoint` вида `c-ams-01.discord.gg` и `token`. Клиент резолвит endpoint, устанавливает WebSocket (wss) на 443 для сигналинга и параллельно поднимает WebRTC-сессию к тому же хосту по UDP.
Ключевой момент: endpoint — это отдельный субдомен, а не `discord.com`. Блокировки на уровне SNI/DNS часто ловят `discord.com` и `gateway.discord.gg`, но забывают про `c-*.discord.gg`. Отсюда и «полурабочее» состояние: чат жив, голос мёртв.
Если вы проксируете только `discord.com`, endpoint голосового сервера резолвится напрямую и уходит в блок. Проверить легко:
```bash
dig +short c-ams-01.discord.gg AAAA
dig +short c-ams-01.discord.gg A
```
Если AAAA возвращает адрес, а A — пусто, значит DNS отдаёт IPv6-путь. Это и есть зацепка: гнать голос по IPv6, где блокировка часто мягче или отсутствует.
Схема: HTTP API через прокси, RTC через IPv6
Рабочая архитектура выглядит так. TCP-трафик (API, gateway, CDN) идёт через SOCKS5/HTTP-прокси. UDP-медиа идёт напрямую по IPv6 — если у вас есть нативный IPv6 или туннель. Discord поддерживает IPv6 на голосовых серверах, и это спасает.
Почему не гнать всё через IPv6? Потому что не все CDN-эндпоинты Discord имеют AAAA-записи, а некоторые IPv6-маршруты до Европы кривые. Гибрид надёжнее: контроль — через прокси, медиа — по IPv6.
Проверить, есть ли у вас рабочий IPv6 до Discord:
```bash
curl -6 -sS -o /dev/null -w "%{http_code} %{time_connect}\n" https://discord.com/api/v10/gateway
curl -6 -sS -o /dev/null -w "%{http_code} %{time_connect}\n" https://c-ams-01.discord.gg/
```
Второй запрос вернёт что-то вроде 400 или 404 — это нормально, хост живой, просто не любит голый GET. Главное — `time_connect` в пределах 40-80 мс до Европы. Если 300+ — голос будет дёргаться.
Настройка прокси-клиента: что завернуть, а что пустить напрямую
Правило простое: в прокси — `discord.com`, `gateway.discord.gg`, `cdn.discordapp.com`, `media.discordapp.net`. Напрямую по IPv6 — `*.discord.gg` (голосовые endpoint'ы) и UDP целиком.
В sing-box или Xray это описывается правилами маршрутизации. Пример для sing-box (JSON-фрагмент route):
```json
{
"route": {
"rules": [
{ "domain_suffix": ["discord.gg"], "outbound": "direct" },
{ "domain_suffix": ["discord.com", "discordapp.com", "discordapp.net"], "outbound": "proxy" },
{ "network": "udp", "port": [50000, 65535], "outbound": "direct" }
]
}
}
```
Порядок правил важен: `discord.gg` должен матчиться раньше, чем `discord.com`, иначе голосовые endpoint'ы уедут в прокси. Порты 50000-65535 — типичный диапазон медиа-портов Discord; UDP на них пускаем напрямую, чтобы ICE собрал host-кандидатов и не зависел от релея.
Отдельно проверьте, что клиент вообще предпочитает IPv6. В Windows это `netsh interface ipv6 show prefixpolicies`, в Linux — `sysctl net.ipv6.conf.all.use_tempaddr` и таблица `gai.conf`. Если IPv4 в приоритете, Discord возьмёт A-запись и уйдёт в блок, даже при живом IPv6.
Проверка ICE-кандидатов: где ломается RTC
Когда Discord не может установить голос, он пишет в лог (включите в настройках «Debug» → «Voice») список кандидатов. Нормальная картина: 2-4 host-кандидата, 1-2 srflx, иногда relay. Если host-кандидатов ноль — значит UDP не уходит, и весь трафик пытается идти через TURN, который тоже заблокирован.
Быстрый способ проверить UDP до голосового сервера — не ждать Discord, а постучаться STUN-запросом. Discord использует стандартный STUN (RFC 5389) на порту 3478 и собственные relay. Простейший тест через Python:
```python
import socket, struct, os
STUN Binding Request, RFC 5389
tid = os.urandom(12)
pkt = struct.pack(">HHI", 0x0001, 0x0000, 0x2112A442) + tid
s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
s.settimeout(3)
try:
s.sendto(pkt, ("stun.l.google.com", 19302))
data, addr = s.recvfrom(1024)
print("STUN OK from", addr, "len", len(data))
except socket.timeout:
print("UDP/STUN timeout — IPv6 UDP блокирован")
```
Если таймаут — не тратьте время на настройку Discord, чините UDP. Часто виноват провайдер, режущий UDP вне 53/123, или локальный фаервол.
Кейс: nginx как TCP-релей для gateway, голос — мимо
Пример из практики. Сервер на Ubuntu 22.04, nginx 1.24, задача — завернуть Discord API и gateway через релей, а голос оставить прямым. Конфиг stream-блока:
```nginx
stream {
upstream discord_gw {
server gateway.discord.gg:443;
}
server {
listen 8443;
proxy_pass discord_gw;
proxy_connect_timeout 5s;
proxy_timeout 300s;
}
}
```
Клиент ходит на `127.0.0.1:8443` для gateway. Голосовые endpoint'ы `c-*.discord.gg` резолвятся напрямую и идут по IPv6. Проблема была в том, что клиентский DNS отдавал A-запись для `c-ams-01.discord.gg`, и голос уходил в блок. Решение — форсировать AAAA через локальный dnsmasq с правилом `filter-AAAA` наоборот, либо прописать статический AAAA в hosts. После правки `time_connect` до голосового упал с 800 мс (через мёртвый релей) до 52 мс напрямую. Голос заработал.
Кейс: SOCKS5 с UDP ASSOCIATE и почему он не спасает
Другой сценарий: клиент настроен на SOCKS5 с поддержкой UDP. Прокси-сервер (3proxy) поднят, UDP ASSOCIATE отвечает. Казалось бы, всё должно работать. На деле — Discord устанавливает голос, но с задержкой 200-400 мс и периодическими обрывами.
Причина: UDP-релей через SOCKS5 добавляет хоп. Пакет идёт клиент → прокси → Discord, обратно так же. Плюс 3proxy не оптимизирован под RTP: буферизация, отсутствие приоритизации. Jitter растёт до 80-120 мс, Discord начинает рвать соединение по таймауту.
Цифры для сравнения (замер через `mtr` и встроенный Discord RTT):
| Путь | RTT медиа | Jitter | Обрывы за 10 мин |
|---|---|---|---|
| Прямой IPv6 | 48 мс | 6 мс | 0 |
| SOCKS5 UDP relay | 210 мс | 95 мс | 4 |
| TURN relay | 130 мс | 30 мс | 1 |
Вывод: SOCKS5 UDP — костыль для голоса. Годится как аварийный вариант, но не для постоянной работы.
IPv6-туннель, если провайдер не даёт нативный
Не у всех есть нативный IPv6. Туннель через Hurricane Electric (tunnelbroker.net) или аналогичный брокер даёт /64 и рабочий маршрут. Задержка добавляется, но для голоса 20-30 мс терпимо.
Настройка на Linux через `sit` или `ip6tnl`:
```bash
ip tunnel add he6 mode sit remote 216.66.84.46 local 203.0.113.10 ttl 255
ip link set he6 up
ip -6 addr add 2001:470:1f0a:1::2/64 dev he6
ip -6 route add ::/0 via 2001:470:1f0a:1::1 dev he6
```
После этого `curl -6` до Discord должен отвечать. Проверьте MTU: у sit-туннеля он 1480, у 6in4 — тоже меньше 1500. Если MTU не согласован, крупные RTP-пакеты будут фрагментироваться и теряться. Discord шлёт медиа пакетами до 1200 байт, так что обычно проходит, но `ping6 -s 1400` стоит прогнать.
DNS: где чаще всего теряется IPv6-путь
Discord резолвит endpoint через системный резолвер. Если в `/etc/resolv.conf` стоит провайдерский DNS, он может отдавать только A-записи для `c-*.discord.gg` — намеренно или из-за кэша. Тогда IPv6-путь не используется, хоть он и есть.
Решение — локальный резолвер с явным предпочтением AAAA. В dnsmasq: `option filter-AAAA` не нужен, наоборот, добавьте `--query-port` и убедитесь, что upstream отдаёт AAAA. Проверка:
```bash
dig @1.1.1.1 c-ams-01.discord.gg AAAA +short
dig @1.1.1.1 c-ams-01.discord.gg A +short
```
Если AAAA есть у Cloudflare, а у вашего резолвера нет — меняйте резолвер. Мелочь, а ломает весь голос.
Что делать с relay-кандидатами
Discord держит собственные TURN-серверы на случай, если P2P не собирается. Их адреса тоже в `*.discord.gg`. Если вы блокируете весь `discord.gg` в прокси, relay-кандидаты отваливаются, и остаётся только прямой путь. Иногда это плюс (меньше задержка), иногда минус (если прямой путь режут).
Практика: оставьте relay доступным, но с низким приоритетом. В ICE приоритет кандидата считается по формуле из RFC 8445; host-кандидаты всегда выше relay. Значит, если UDP по IPv6 работает, Discord выберет прямой путь сам, а relay будет страховкой. Не блокируйте его зря.
Итоговая конфигурация и метрики
Соберите всё вместе: TCP-контроль через прокси, UDP-медиа по IPv6 напрямую, DNS с AAAA, MTU 1480 для туннеля. Проверяйте по трём метрикам: `time_connect` до голосового endpoint (норма < 80 мс), jitter в Discord (норма < 20 мс), число обрывов за сессию (норма 0).
Если голос всё равно «RTC Connecting» — включайте Debug-лог, смотрите, какие кандидаты собрались. Нет host-кандидатов — UDP режется. Есть только relay — прямой путь недоступен. Есть host, но соединение не устанавливается — проблема на стороне endpoint, проверьте SNI и порт.
Для стабильной работы голосового Discord через IPv6 достаточно трёх вещей: рабочий AAAA-резолв, незаблокированный UDP наружу и прямой маршрут до `c-*.discord.gg`. Прокси нужен только для TCP-части; пытаться протащить через него RTP — терять задержку и нервы.