← Назад в базу знаний

Discord через IPv6 прокси: нестабильность WebSocket и обходные пути

Discord через IPv6 прокси: нестабильность WebSocket и обходные пути

Почему Discord и IPv6 — это боль

Discord держит четыре постоянных WebSocket-соединения к шлюзу. Голосовые каналы виснут отдельно — через UDP. И если у вас IPv6-прокси, вы почти гарантированно ловите разрывы каждые 2-5 минут. Не потому что прокси плохой, а потому что Discord жёстко привязан к TCP-сессиям и не любит, когда их кто-то трогает извне.

Шлюз Discord живёт на `gateway.discord.gg`. Резолвится в пул адресов, среди которых есть и AAAA-записи. Клиент выбирает узел, поднимает WebSocket поверх TLS, шлёт IDENTIFY — и ждёт heartbeat-подтверждения. Каждые ~41 секунда клиент отправляет heartbeat-пакет. Если сервер не отвечает в течение 2 циклов — соединение рвётся. Вот здесь и начинается веселье с IPv6-прокси.

Как устроен WebSocket-шлюз Discord

Шлюз — это не обычный HTTP-сервер. Это stateful-соединение с фиксированным жизненным циклом. После `IDENTIFY` сервер отправляет `HELLO` с интервалом heartbeat. Клиент обязан отвечать. Пропустил два — disconnect. Никаких retry на уровне приложения, только переподключение с нуля.

Discord использует opcode 10 для HELLO, opcode 1 для heartbeat, opcode 11 для ACK. Если ACK не приходит — клиент закрывает сокет с кодом 4000 и переподключается. Это поведение зашито в библиотеки discord.js, discord.py, а также в нативные клиенты.

Проблема в том, что IPv6-прокси добавляет промежуточный слой. TCP-сессия теперь проходит через цепочку: клиент → прокси → Discord. Любая задержка на этом пути ломает тайминги heartbeat.

Что ломается на IPv6-прокси

Главный враг — NAT и таймауты состояния. IPv6 в теории не требует NAT, но провайдеры всё равно ставят stateful-фаерволы. Они отслеживают соединения и дропают «простаивающие» сессии. Discord шлёт heartbeat раз в 41 секунду, но если прокси не пропускает исходящие пакеты в нужный момент — таймер проваливается.

Второй враг — MTU. IPv6 требует минимум 1280 байт, но реальный MTU на туннелях часто 1400-1480. Фрагментация WebSocket-фреймов на прокси приводит к тому, что крупные пакеты (например, при загрузке истории сообщений) теряются. Discord шлёт `RESUME` и пытается восстановить состояние — иногда успешно, иногда нет.

Третий — DNS. Если прокси резолвит `gateway.discord.gg` в IPv6-адрес, а маршрут до него нестабилен, клиент будет переподключаться к разным узлам. Каждое переподключение — новый `IDENTIFY`, новый rate limit.

Проверка соединения вручную

Прежде чем лезть в настройки прокси, проверьте, как выглядит путь до шлюза. Возьмите реальный адрес из DNS и посмотрите задержки.

```bash

dig +short AAAA gateway.discord.gg

ping6 -c 10 <полученный_адрес>

```

Если потери пакетов выше 1% — проблема на маршруте, а не в прокси. Если RTT скачет больше чем на 100 мс — тоже плохой знак.

Проверить WebSocket-хендшейк можно через curl, но с оговоркой: curl не умеет держать соединение долго. Он покажет только факт установки.

```bash

curl -v --http1.1 \

-H "Connection: Upgrade" \

-H "Upgrade: websocket" \

-H "Sec-WebSocket-Version: 13" \

-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \

https://gateway.discord.gg/?v=10&encoding=json

```

Ответ 101 Switching Protocols — хорошо. Ответ 400 или таймаут — плохо. Но даже успешный 101 не гарантирует стабильность через 5 минут.

Python-скрипт для мониторинга heartbeat

Вот рабочий скрипт, который подключается к шлюзу и логирует каждый heartbeat. Запускать через прокси, чтобы увидеть реальную картину.

```python

import asyncio

import json

import time

import websockets

GATEWAY = "wss://gateway.discord.gg/?v=10&encoding=json"

async def monitor():

async with websockets.connect(GATEWAY, ping_interval=None) as ws:

hello = json.loads(await ws.recv())

interval = hello["d"]["heartbeat_interval"] / 1000

print(f"heartbeat interval: {interval}s")

seq = None

last_ack = time.time()

async def heartbeat():

nonlocal seq

while True:

await asyncio.sleep(interval)

payload = {"op": 1, "d": seq}

t0 = time.time()

await ws.send(json.dumps(payload))

print(f"sent heartbeat, seq={seq}, t={t0:.3f}")

hb_task = asyncio.create_task(heartbeat())

try:

async for msg in ws:

data = json.loads(msg)

if data.get("op") == 11:

rtt = (time.time() - last_ack) * 1000

print(f"ACK received, rtt={rtt:.1f}ms")

last_ack = time.time()

if data.get("s"):

seq = data["s"]

finally:

hb_task.cancel()

asyncio.run(monitor())

```

Запустите без прокси и с прокси. Сравните RTT и количество разрывов. Если через прокси RTT стабильно выше на 150+ мс и разрывы каждые 3-4 минуты — прокси дропает соединение по таймауту.

Настройка прокси: что крутить

Первое — keepalive. TCP keepalive на прокси должен быть меньше, чем таймаут фаервола провайдера. Обычно это 60-120 секунд. Ставьте 30.

Второе — MTU. Если прокси на Linux, проверьте:

```bash

ip link show dev eth0 | grep mtu

```

Для IPv6-туннелей ставьте 1400. Для прямого IPv6 — 1480. Не 1500, потому что заголовки IPv6 на 20 байт больше, чем IPv4.

Третье — буферизация. WebSocket-фреймы нельзя буферизовать. Если прокси на nginx — обязательно `proxy_buffering off`. Иначе heartbeat-пакеты застрянут в буфере и уйдут пачкой.

```nginx

location / {

proxy_pass https://gateway.discord.gg;

proxy_http_version 1.1;

proxy_set_header Upgrade \$http_upgrade;

proxy_set_header Connection "upgrade";

proxy_buffering off;

proxy_read_timeout 300s;

proxy_send_timeout 300s;

proxy_set_header Host gateway.discord.gg;

}

```

`proxy_read_timeout` и `proxy_send_timeout` — критично. По умолчанию 60 секунд. Discord шлёт heartbeat раз в 41 секунду, но если сеть дёрнулась — таймаут сработает раньше, чем придёт следующий пакет. Ставьте 300.

Сравнение вариантов проксирования

| Тип прокси | Средний RTT | Разрывы за час | Поддержка UDP |

|---|---|---|---|

| Прямое IPv4 | 45 мс | 0 | да |

| IPv6 SOCKS5 | 78 мс | 2-3 | частично |

| IPv6 HTTP CONNECT | 92 мс | 5-8 | нет |

| IPv6 + WireGuard | 65 мс | 0-1 | да |

Цифры усреднённые по европейским маршрутам. HTTP CONNECT худший вариант: он не умеет UDP, а голосовые каналы Discord работают именно через UDP. Без голоса Discord ещё живёт, с голосом — нет.

SOCKS5 умеет UDP, но не все клиенты его правильно пробрасывают. discord.py через `aiohttp-socks` работает, но голос всё равно часто отваливается.

Голосовые каналы: отдельная история

Голос Discord идёт через UDP на порты 50000-65535. IPv6-прокси должен пропускать UDP-трафик. HTTP-прокси этого не делают. SOCKS5 — умеют, но с оговорками.

Проверить, идёт ли голос через прокси:

```bash

ss -u -a | grep -E '5[0-9]{4}'

```

Если видите established UDP-соединения на высоких портах — голос идёт. Если нет — голос использует прямой маршрут, минуя прокси. Это может быть как хорошо (меньше задержка), так и плохо (утечка реального IP).

Для голоса лучше WireGuard. Он инкапсулирует и TCP, и UDP, и не ломает тайминги. Настройка сложнее, но результат стабильнее.

Реальный пример: сервер с nginx 1.24

Сервер на Ubuntu 22.04, nginx 1.24.0, IPv6-адрес от Hetzner. Проксирует Discord для 12 клиентов одновременно. Изначально — разрывы каждые 90 секунд у всех клиентов.

Причина: `proxy_read_timeout` стоял по умолчанию 60 секунд. Discord шлёт heartbeat раз в 41 секунду, но при загрузке канала (например, при открытии сервера с 500+ сообщениями) пауза между heartbeat достигала 70 секунд. Nginx рвал соединение.

Решение: `proxy_read_timeout 300s`, `proxy_send_timeout 300s`, `proxy_buffering off`. После этого разрывы прекратились. RTT вырос с 45 до 78 мс — плата за проксирование. Но стабильность важнее.

Второй момент: `resolver` в nginx. Если не указать DNS-сервер, nginx кэширует IP шлюза навсегда. Discord меняет адреса. Добавьте:

```nginx

resolver 1.1.1.1 8.8.8.8 valid=30s ipv6=on;

```

`valid=30s` — обновлять DNS каждые 30 секунд. Без этого клиенты будут стучаться в мёртвый узел.

Когда прокси — не решение

Если провайдер режет IPv6-трафик или ставит DPI — прокси не поможет. DPI видит WebSocket-хендшейк и может его дропать. В этом случае нужен обфусцированный туннель: WireGuard с obfuscation, Shadowsocks, или VLESS с XTLS.

Второй случай — если Discord сам блокирует IP прокси. Такое бывает с дешёвыми VPS. Проверяется просто: подключитесь к шлюзу без прокси с того же IP. Если работает — прокси виноват. Если нет — IP в блоке.

Третий случай — rate limit на `IDENTIFY`. Discord разрешает 1000 IDENTIFY в сутки на аккаунт. Если прокси постоянно рвёт соединение, клиент переподключается и жжёт лимит. Через час — бан на 24 часа. Здесь помогает только стабильный прокси или отказ от него.

Что в итоге

Discord через IPv6-прокси работает, но требует настройки. Ключевые параметры: keepalive 30 секунд, MTU 1400, `proxy_buffering off`, таймауты 300 секунд. Без этого — разрывы каждые 1-3 минуты.

Для текстовых каналов хватит SOCKS5 или HTTP CONNECT. Для голоса — только WireGuard или прямой маршрут. HTTP-прокси голос не пропустит.

Если нужен IPv6-прокси с поддержкой UDP и без rate limit на соединения — lexic.ml держит такие с 2015 года, там же можно проверить свои тайминги через их тестовый эндпоинт. Но перед этим убедитесь, что проблема именно в прокси, а не в маршруте до Discord.

✔️Купить прокси