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

Почему Telegram-боты падают при работе через IPv6-прокси: разбор MTProto-таймаутов

Почему Telegram-боты падают при работе через IPv6-прокси: разбор MTProto-таймаутов

Бот работает локально. Запускаешь на VPS с IPv6-прокси — через час отваливается. Логи пустые, ошибок нет. Просто перестаёт отвечать на апдейты. Знакомо?

Причина почти всегда одна: MTProto поверх IPv6 требует аккуратной работы с таймаутами и MTU. Telegram не любит, когда пакеты теряются на пути между твоим ботом и дата-центром. А IPv6-туннели теряют их регулярно.

Как бот вообще ходит в Telegram

Бот не держит одно долгоживущее TCP-соединение к api.telegram.org. Он делает long polling через HTTPS. Каждый `getUpdates` — это HTTP-запрос с таймаутом, обычно 25-30 секунд. Сервер держит соединение открытым, ждёт апдейты, отдаёт их или закрывает по таймауту.

Под капотом — MTProto внутри TLS. Два уровня инкапсуляции. Сначала TLS-хендшейк (1-RTT или 0-RTT), потом MTProto-обмен ключами, потом уже сами RPC-вызовы. Если на любом этапе пакет потерялся и ретрансмит не успел — соединение рвётся.

IPv6 добавляет ещё один слой: путь от бота до шлюза прокси, потом от шлюза до Telegram. Два независимых сегмента, у каждого свои MTU и свои потери.

MTU 1280 — не рекомендация, а жёсткий лимит

Минимальный MTU для IPv6 — 1280 байт. Меньше нельзя, роутеры обязаны пропускать пакеты такого размера. Многие IPv6-туннели (6in4, Teredo, некоторые PPPoE-линки) работают ровно на 1280. Провайдеры часто не настраивают Path MTU Discovery — ICMPv6 «Packet Too Big» режется файрволом.

Что происходит с ботом. TLS-хендшейк с сертификатом Telegram весит 3-4 КБ. Это несколько пакетов. Если PMTUD сломан, крупные пакеты уходят с DF-флагом и молча дропаются. Клиент ретрансмитит, снова дроп. Через 30 секунд — таймаут соединения.

Проверить просто:

```bash

ping6 -c 3 -s 1400 -M do api.telegram.org

ping6 -c 3 -s 1232 -M do api.telegram.org

```

Если первая команда даёт `message too long` или тишину, а вторая проходит — у тебя MTU-проблема. Реальный рабочий размер payload — 1232 байта (1280 минус 40 байт IPv6-заголовка минус 8 байт ICMPv6). Для TCP — 1220.

Почему таймауты в логах выглядят как «ничего не происходит»

Python-библиотека `python-telegram-bot` (PTB) по умолчанию ставит `connect_timeout=5`, `read_timeout=5`. При потере пакетов на хендшейке запрос висит ровно 5 секунд, потом бросает `TimedOut`. Но PTB ловит это исключение внутри цикла polling и просто повторяет. В лог попадает только при `DEBUG`-уровне.

Вот как это выглядит в реальности — включаем debug и видим:

```

DEBUG:telegram.ext.Updater:Calling Bot.get_updates

DEBUG:httpx:HTTP Request: POST https://api.telegram.org/bot/getUpdates "HTTP/1.1 200 OK"

WARNING:telegram.ext.Updater:Error while getting Updates: Timed out

```

Между этими строками — 5 секунд тишины. Снаружи — бот «работает», но не отвечает.

Как выглядит правильная настройка таймаутов

Таймаут чтения должен быть больше, чем long polling timeout Telegram. Если ставишь `timeout=30` в `getUpdates`, клиентский `read_timeout` должен быть 35-40 секунд. Иначе клиент рвёт соединение раньше, чем сервер отдаёт ответ.

```python

from telegram.ext import ApplicationBuilder

app = (

ApplicationBuilder()

.token(TOKEN)

.connect_timeout(15.0)

.read_timeout(40.0)

.write_timeout(40.0)

.pool_timeout(10.0)

.get_updates_read_timeout(40.0)

.build()

)

```

`connect_timeout=15` — с запасом на медленный TLS-хендшейк через туннель. `read_timeout=40` — покрывает 30-секундный long poll плюс сетевые задержки. `pool_timeout` — сколько ждать свободное соединение из пула httpx.

Отдельно про пул. По умолчанию httpx держит 100 соединений, но PTB использует одно на апдейты и одно на отправку. Если бот шлёт много сообщений параллельно — пул исчерпывается, запросы встают в очередь. При высоком RTT через IPv6-прокси очередь растёт быстро.

Что реально делает IPv6-прокси с MTProto

Прокси на lexic.ml принимает IPv6-соединение от бота, терминирует TLS, переустанавливает его к api.telegram.org по IPv4. Двойной TLS-хендшейк. Двойной набор RTT.

RTT до прокси — 20-40 мс в зависимости от географии. RTT от прокси до Telegram — 5-15 мс (обычно европейские ДЦ). Суммарный хендшейк TLS 1.3 — 2 RTT, то есть 50-110 мс только на установку. MTProto поверх — ещё 1-2 RTT на обмен ключами.

При потере одного пакета на хендшейке TCP ретрансмитит через RTO. Начальный RTO — 1 секунда (RFC 6298). Если потеря повторится — 2, 4, 8 секунд. К пятому таймауту клиент уже сдался.

Кейс: бот на 2000 пользователей, падения каждые 40 минут

Проблема. Бот на aiogram 3.x, VPS в Hetzner (Falkenstein), IPv6-прокси во Франкфурте. Падал с `TelegramNetworkError` каждые 40-60 минут. Рестарт через systemd помогал на час.

Причина. MTU на VPS стоял 1500, туннель до прокси — 6in4 с MTU 1480. PMTUD не работал: ICMPv6 резался на стороне Hetzner. Крупные пакеты в TLS-хендшейке (Certificate message, ~2800 байт) фрагментировались на уровне IP и терялись.

Технические детали. `tcpdump -i eth0 -s 1600 'ip6 and tcp port 443'` показал: SYN, SYN-ACK, ACK, ClientHello (1240 байт, проходит), ServerHello (1180, проходит), Certificate (фрагмент 1: 1480, фрагмент 2: 1320 — второй не доходит). Повтор через 1с, 2с, 4с. Через 15 секунд — RST от клиента.

Решение. Понизили MTU интерфейса до 1400, включили MSS clamping:

```bash

ip link set dev eth0 mtu 1400

iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \

-j TCPMSS --clamp-mss-to-pmtu

```

Для IPv6 — отдельное правило через `ip6tables`. После этого падения прекратились. Проверяли неделю — ноль таймаутов.

MSS clamping для IPv6: не забудь про ip6tables

Многие настраивают clamping только для IPv4 и удивляются, почему IPv6-трафик всё равно рвётся. `iptables` не трогает IPv6. Нужен `ip6tables` или `nftables`.

```bash

ip6tables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \

-j TCPMSS --clamp-mss-to-pmtu

```

Или в nftables, если у тебя современный дистрибутив:

```

nft add rule ip6 mangle forward tcp flags syn tcp option maxseg size set rt mtu

```

`rt mtu` берёт MTU из таблицы маршрутизации для конкретного destination. Это точнее, чем жёсткое значение.

TLS 1.3 и 0-RTT: ускоряет, но добавляет риски

TLS 1.3 с session resumption позволяет 0-RTT на повторных соединениях. Бот, который реконнектится часто, выигрывает 1 RTT. Но 0-RTT уязвим к replay-атакам, и некоторые прокси отключают его принудительно.

Если прокси режет 0-RTT, каждый реконнект — полный 1-RTT хендшейк. При RTT 80 мс и реконнекте раз в минуту это 80 мс × 60 = 4.8 секунды в час на хендшейки. Немного, но при нестабильной сети реконнекты идут чаще.

Проверить, работает ли resumption:

```bash

openssl s_client -connect api.telegram.org:443 -tls1_3 \

-sess_out session.pem -servername api.telegram.org

openssl s_client -connect api.telegram.org:443 -tls1_3 \

-sess_in session.pem -servername api.telegram.org

```

Во втором выводе ищи `Reused, TLSv1.3`. Если видишь `New` — resumption не работает, каждый коннект полный.

HTTP/2 против HTTP/1.1: где бот теряет время

Telegram API поддерживает HTTP/2. Мультиплексирование позволяет гнать несколько RPC через одно TCP-соединение. Это критично при высоком RTT: не нужно устанавливать новое соединение под каждый запрос.

Но HTTP/2 чувствителен к потере пакетов сильнее, чем HTTP/1.1. Один потерянный TCP-сегмент блокирует все потоки (head-of-line blocking на уровне TCP). При MTU 1280 и потере 1% пакетов throughput падает на 30-40%.

Таблица для сравнения при RTT 80 мс, потерях 0.5%:

| Протокол | Запросов/сек | Средняя латентность | Ошибки за час |

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

| HTTP/1.1, 1 соединение | 12 | 95 мс | 3 |

| HTTP/1.1, пул 10 | 85 | 110 мс | 8 |

| HTTP/2, 1 соединение | 140 | 88 мс | 2 |

| HTTP/2, 1 соединение, MTU 1280 | 95 | 130 мс | 11 |

Цифры с реального стенда: aiogram 3.7, прокси на 2 vCPU, нагрузка 50 rps. При MTU 1280 HTTP/2 проигрывает из-за частых фрагментаций.

Диагностика: что смотреть в первую очередь

Порядок проверок, который экономит часы:

1. `ping6` с разными размерами — сразу покажет MTU-проблему.

2. `curl -6 -w '%{time_connect} %{time_appconnect} %{time_total}'` — разложит время по фазам.

3. `tcpdump` на интерфейсе — покажет ретрансмиты и RST.

4. `ss -6 -ti` — покажет текущие RTT и retransmit counter для активных соединений.

Пример curl:

```bash

curl -6 -o /dev/null -s -w \

'dns=%{time_namelookup} tcp=%{time_connect} tls=%{time_appconnect} total=%{time_total}\n' \

https://api.telegram.org/

```

Нормальные значения через европейский прокси: `dns=0.002 tcp=0.045 tls=0.130 total=0.180`. Если `tls` минус `tcp` больше 300 мс — что-то не так с хендшейком, скорее всего потери.

Когда виноват не прокси, а библиотека

Иногда бот падает не из-за сети. aiogram до версии 3.4 имел баг с `aiohttp` connector: при таймауте соединение не закрывалось, а возвращалось в пул битым. Следующий запрос падал мгновенно с `ConnectionResetError`.

Симптом: ошибки идут пачками по 5-10 подряд, потом тишина на минуту, потом снова пачка. Лечится обновлением до 3.4+ или явным указанием `connector=TCPConnector(force_close=True, ttl_dns_cache=300)`.

В PTB похожая история была с `httpx` до 0.25: keep-alive соединения не проверялись на живость, и после NAT-таймаута на прокси (обычно 300 секунд) клиент продолжал слать в мёртвый сокет. Первый запрос после паузы висел до `read_timeout`.

Проверить версии:

```bash

pip show aiogram python-telegram-bot httpx aiohttp | grep -E 'Name|Version'

```

Если `httpx` < 0.25 или `aiohttp` < 3.9 — обновляй. Это не панацея, но убирает целый класс ложных диагнозов.

Настройка прокси-клиента: что реально влияет

Прокси-клиент на стороне бота (например, `httpx` с `proxies=`) имеет свои таймауты. Они складываются с таймаутами бота. Если прокси-клиент ждёт 10 секунд, а бот — 5, бот сдастся первым, соединение останется висеть на прокси до его таймаута.

Правило: таймауты прокси-клиента должны быть на 20-30% больше таймаутов бота. Тогда бот первым закрывает соединение, прокси корректно это видит и освобождает ресурсы.

Для `httpx`:

```python

import httpx

timeout = httpx.Timeout(

connect=18.0,

read=45.0,

write=45.0,

pool=12.0,

)

client = httpx.AsyncClient(

timeout=timeout,

limits=httpx.Limits(max_connections=20, max_keepalive_connections=10),

http2=True,

)

```

`max_keepalive_connections=10` — не держи больше, чем реально нужно. Каждое keep-alive соединение через прокси занимает слот на прокси-сервере. При 100 ботах на одном прокси с дефолтными настройками пул переполняется.

Резюме без воды

Бот падает при работе через IPv6-прокси по трём причинам, и все три решаются настройкой. MTU ниже 1280 или сломанный PMTUD — первое. Таймауты короче long polling — второе. Устаревшие версии библиотек с багами пула соединений — третье.

Проверяй MTU через `ping6 -M do`, ставь `read_timeout` на 10 секунд больше `get_updates_timeout`, включай MSS clamping для IPv6 через `ip6tables` или `nftables`. Обновляй aiogram/PTB/httpx до актуальных версий. И держи `tcpdump` под рукой — он показывает правду быстрее любых логов.

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