TikTok Live через IPv6 прокси: почему стрим обрывается на 30-й минуте и как это лечится
Содержание
- Что вообще происходит под капотом
- Почему именно 30 минут
- Как выглядит обрыв в логах
- Что крутить на прокси
- Настройка SOCKS5, которая держит стрим
- Проверка через curl и python
- Кейс: стрим на 1440p через немецкий прокси
- Кейс: conntrack переполнен на общем прокси
- Кейс: RTMPS и TLS-ресессия
- Таблица: что и где крутить
- Почему IPv6 хуже IPv4 в этом сценарии
- Чеклист перед стримом
- !/bin/bash
- Что делать, если ничего не помогло
Стрим на TikTok Live через прокси падает. Не сразу — минут через тридцать. Иногда сорок. Картинка замирает, зрители пишут «чё залип», а через несколько секунд соединение рвётся. Перезапуск помогает — и снова полчаса ровной работы. Это не случайность и не «плохой прокси». Это конкретная комбинация таймаутов, MTU и того, как TikTok держит медиапоток через RTMP и QUIC.
Что вообще происходит под капотом
TikTok Live для OBS и мобильных стримеров работает преимущественно через RTMP на входе (порт 1935) и RTMPS (443). Внутри — H.264 видео и AAC аудио, нарезанные на флаги. Со стороны зрителя всё раздаётся через CDN по HLS или QUIC. Стримеру важно только восходящее соединение: TCP-сессия до ingest-сервера.
Когда между OBS и ingest стоит прокси, весь TCP-трафик идёт через него. И вот здесь начинается зоопарк. Прокси может быть SOCKS5, HTTP CONNECT или прозрачный туннель на уровне TUN. Каждый из них по-своему держит соединение, по-своему обрабатывает keepalive и по-своему относится к длинным сессиям.
RTMP сам по себе долгоживущий. Одна TCP-сессия может жить часами. Промежуточное звено должно это пережить. Большинство дешёвых прокси — не переживает.
Почему именно 30 минут
Тридцать минут — это не магия TikTok. Это дефолтные таймауты, которые встречаются в куче мест одновременно. NAT-таблица на гипервизоре прокси-сервера часто имеет conntrack timeout 1800 секунд для established TCP. Ровно 30 минут. Если трафик идёт непрерывно, conntrack продлевается. Но RTMP шлёт данные неравномерно: паузы между флагами, keyframe interval, тишина в эфире — всё это создаёт микроокна, где keepalive не проходит.
Второй подозреваемый — сам прокси-демон. Squid по умолчанию рубит соединения по `read_timeout` (15 минут) и `request_timeout`. 3proxy с дефолтным конфигом держит сессию ограниченное время. SOCKS5-серверы на базе dante имеют свой `command_timeout`. Сложите два-три таймаута — получите те самые полчаса.
Третий — MTU. IPv6 не фрагментирует пакеты на маршрутизаторах, только на источнике. Если MTU туннеля 1280, а OBS шлёт пакеты по 1500, они дропаются. TCP ретраит, но медленно. За полчаса накапливается достаточно ретрансмиссий, чтобы TikTok посчитал поток нестабильным и разорвал сессию со стороны ingest.
Как выглядит обрыв в логах
Прежде чем лечить, надо увидеть. В логах OBS при обрыве обычно:
```
21:34:12.441: [rtmp stream: 'simple_stream'] Connection to rtmps://global-rtmp.tiktok.com:443 successful
21:34:12.442: [rtmp stream: 'simple_stream'] Socket send buffer is 65536 bytes
...
22:04:15.113: [rtmp stream: 'simple_stream'] WriteN, RTMP send error 10054 (10038)
22:04:15.113: [rtmp stream: 'simple_stream'] WriteN, RTMP send error 10038 (41)
22:04:15.113: [rtmp stream: 'simple_stream'] Disconnected from rtmps://global-rtmp.tiktok.com:443
```
`10054` — это WSAECONNRESET на Windows. Соединение сброшено удалённой стороной. Смотрим на таймстемпы: 21:34 → 22:04. Ровно 30 минут и 3 секунды. Это не совпадение, это conntrack.
На стороне прокси в этот же момент в `dmesg`:
```
nf_conntrack: table full, dropping packet
```
или в `conntrack -S` растёт `insert_failed`. Гипервизор не успевает продлевать записи, потому что таблица переполнена соседними клиентами.
Что крутить на прокси
Первое — conntrack. На сервере с IPv6-прокси проверьте:
```bash
sysctl net.netfilter.nf_conntrack_tcp_timeout_established
sysctl net.netfilter.nf_conntrack_tcp_timeout_unacknowledged
sysctl net.netfilter.nf_conntrack_max
```
Если `established` = 432000 (5 дней) — хорошо. Если 1800 — вот ваш обрыв. Поднимите до 86400 и увеличьте `nf_conntrack_max` хотя бы до 262144. Это не панацея, но убирает самый частый источник.
Второе — keepalive. TCP keepalive по умолчанию в Linux — 7200 секунд до первого проба. За это время NAT успеет трижды забыть про сессию. Снижайте:
```bash
sysctl -w net.ipv4.tcp_keepalive_time=60
sysctl -w net.ipv4.tcp_keepalive_intvl=15
sysctl -w net.ipv4.tcp_keepalive_probes=4
```
Для IPv6 то же самое, но параметры общие — они в `net.ipv4`, историческое наследие. Работают и на v6-сокетах.
Третье — MTU. Узнайте реальный MTU пути:
```bash
ping6 -M do -s 1452 -c 3 ingest.global.tiktok.com
```
Если пакеты 1480 байт (1452 + 28 заголовков) проходят, а 1500 — нет, у вас PMTU 1480. Ставьте на интерфейсе прокси MTU 1480 или ниже. Для туннелей (WireGuard, GRE) — 1420 безопасно.
Настройка SOCKS5, которая держит стрим
Дефолтный dante рвёт долгие сессии. Конфиг, который работает с RTMP:
```
logoutput: /var/log/socks.log
internal: eth0 port = 1080
external: eth0
clientmethod: none
socksmethod: username
user.privileged: root
user.unprivileged: nobody
client pass {
from: 0.0.0.0/0 to: 0.0.0.0/0
log: error
}
socks pass {
from: 0.0.0.0/0 to: 0.0.0.0/0
command: bind connect udpassociate
log: error
socksmethod: username
}
```
Ключевое — не ставить `timeout.negotiate` и `timeout.io` в маленькие значения. По умолчанию dante держит IO-таймаут 0 (бесконечно), но некоторые сборки ставят 86400. Проверьте. Если 86400 — это сутки, приемлемо. Если меньше — стрим не доживёт.
Для 3proxy аналогично:
```
timeout 86400
connlim 100
noconnect
allow * * *
```
`timeout 86400` — сутки на сессию. `connlim` ограничивает одновременные соединения с одного IP, чтобы один клиент не съел всю таблицу.
Проверка через curl и python
Быстрый тест, что прокси вообще держит долгую сессию — не RTMP, а обычный медленный HTTP. Стримим с сервера бесконечный ответ и читаем через прокси:
```bash
curl -x socks5h://user:pass@[2a01:4f8:1c1c:abcd::1]:1080 \
--max-time 3600 \
-o /dev/null \
-w "connected=%{time_connect} total=%{time_total}\n" \
https://speed.hetzner.de/100MB.bin
```
Если на 1800-й секунде `curl` падает с `Recv failure: Connection reset by peer` — таймаут подтверждён. Если доживает до 3600 — прокси в порядке, копайте в OBS или TikTok.
Для точной диагностики — питон, который держит TCP-сессию и логирует разрывы:
```python
import socket, ssl, time, sys
HOST = "ingest.global.tiktok.com"
PORT = 443
PROXY = ("2a01:4f8:1c1c:abcd::1", 1080)
s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
s.settimeout(30)
s.connect(PROXY)
s.sendall(b"\x05\x01\x00")
if s.recv(2) != b"\x05\x00":
sys.exit("socks handshake failed")
host_b = HOST.encode()
req = b"\x05\x01\x00\x03" + bytes([len(host_b)]) + host_b + PORT.to_bytes(2, "big")
s.sendall(req)
resp = s.recv(10)
if resp[1] != 0:
sys.exit(f"socks connect failed: {resp[1]}")
ctx = ssl.create_default_context()
tls = ctx.wrap_socket(s, server_hostname=HOST)
start = time.time()
try:
while time.time() - start < 3600:
tls.sendall(b"\x00" * 1024)
time.sleep(1)
except Exception as e:
print(f"died after {time.time() - start:.0f}s: {e}")
```
Запускаете, идёте пить кофе. Вернулись через час — либо `died after 1802s`, либо скрипт отработал до конца. Второй вариант означает, что прокси держит, а проблема в чём-то другом.
Кейс: стрим на 1440p через немецкий прокси
Клиент стримил на TikTok Live в 1440p60, битрейт 8000 кбит/с, через IPv6-прокси в Франкфурте. OBS показывал стабильные 0% дропов, но каждые 28-32 минуты — обрыв. Логи OBS: `RTMP send error 10054`. Логи прокси: пусто, никаких ошибок.
Причина оказалась в MTU. Прокси стоял на VPS с WireGuard-туннелем до аплинка, MTU интерфейса 1420, но `wg0` собран с дефолтным 1420, а физический интерфейс — 1500. PMTU discovery по IPv6 работал криво: ICMPv6 Packet Too Big терялся на одном из хопов, OBS не получал уведомление и продолжал шпарить пакетами по 1500. Каждый такой пакет дропался на туннеле, TCP ретраил, RTT рос с 40 мс до 800+ мс. TikTok видел «залипание» и рвал сессию.
Решение: `ip link set wg0 mtu 1380` на прокси и `ip6tables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu`. После этого стрим шёл 6 часов без единого разрыва.
Кейс: conntrack переполнен на общем прокси
Другой клиент — стример с аудиторией 2000+ зрителей, использовал общий прокси на 50 клиентов. Стрим падал стабильно на 30-й минуте. `conntrack -S` на сервере показал `insert_failed: 148293` за сутки. Таблица на 65536 записей, при 50 клиентах с кучей параллельных соединений (браузер, тикток-студия, OBS, чат) — переполнение.
Причина: `nf_conntrack_max` дефолтный, а `tcp_timeout_established` = 432000. Записи висят по пять дней, новые не влезают. TikTok-сессия не может продлить conntrack-запись, ядро её эвиктит, TCP RST уходит в OBS.
Решение: `nf_conntrack_max = 524288`, `tcp_timeout_established = 86400`, плюс `nf_conntrack_tcp_be_liberal = 1` для толерантности к out-of-window пакетам. После правок — 12 часов аптайма стрима без разрывов.
Кейс: RTMPS и TLS-ресессия
Третий случай — стрим через RTMPS (порт 443). OBS подключался, работал 25-35 минут, потом `SSL_write: error 0x0307F07C`. Прокси — HTTP CONNECT на базе squid 5.7.
Squid по умолчанию имеет `read_timeout 15 minutes` и `request_timeout 5 minutes`. Для CONNECT-туннеля применяется `read_timeout`, но с нюансом: если трафик идёт, таймаут продлевается. Проблема была в том, что при низком битрейте (1500 кбит/с на тихом эфире) между пакетами возникали паузы до 200-300 мс, и squid считал соединение «простаивающим».
Плюс сам TikTok периодически инициирует TLS renegotiation на RTMPS — примерно раз в 30 минут. Во время ресессии трафик на пару секунд прекращается. Этого хватало, чтобы squid закрыл туннель.
Решение: `read_timeout 4 hours`, `request_timeout 30 minutes`, `client_idle_pconn_timeout 10 minutes` в `squid.conf`. После этого RTMPS-стримы живут часами.
Таблица: что и где крутить
| Параметр | Дефолт | Для стрима | Где |
|---|---|---|---|
| `nf_conntrack_tcp_timeout_established` | 432000 | 86400 | sysctl прокси |
| `nf_conntrack_max` | 65536 | 524288 | sysctl прокси |
| `tcp_keepalive_time` | 7200 | 60 | sysctl прокси |
| `tcp_keepalive_intvl` | 75 | 15 | sysctl прокси |
| squid `read_timeout` | 15m | 4h | squid.conf |
| dante `timeout.io` | 0 (inf) | 86400 | sockd.conf |
| MTU туннеля | 1500 | 1380-1420 | ip link |
| OBS keyframe interval | 0 (auto) | 2 | OBS settings |
Последняя строка не про прокси, но важна. Keyframe interval 2 секунды означает, что каждые 2 секунды OBS шлёт крупный I-frame, который может не пролезть в MTU и фрагментироваться на уровне TCP. Больше фрагментов — больше шансов потерять один и получить ретрансмиссию. При стриме через прокси всегда ставьте keyframe 2, не auto.
Почему IPv6 хуже IPv4 в этом сценарии
IPv4 умеет фрагментироваться на маршрутизаторах. Некрасиво, но пакет дойдёт. IPv6 фрагментируется только на источнике. Если PMTU не согласован — пакет молча дропается, а источник узнаёт об этом только через ICMPv6 Packet Too Big. Этот ICMP часто фильтруют файрволы. Особенно в облаках.
Плюс IPv6-адресация в прокси-пулах часто реализована через один интерфейс с кучей адресов. Conntrack для каждого адреса свой, но таблица общая. Если пул на 1000 адресов, а `nf_conntrack_max` дефолтный — переполнение гарантировано.
И третье: многие IPv6-прокси-сервисы (включая lexic.ml) выдают клиенту /64 или /48 префикс, и клиент сам выбирает адрес для исходящего соединения. Если адрес меняется в середине сессии — TikTok видит смену источника и рвёт соединение. Проверьте, что прокси-клиент не ротирует адрес при активной сессии.
Чеклист перед стримом
Запустите это на прокси за час до эфира:
```bash
!/bin/bash
sysctl -w net.netfilter.nf_conntrack_max=524288
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=86400
sysctl -w net.ipv4.tcp_keepalive_time=60
sysctl -w net.ipv4.tcp_keepalive_intvl=15
sysctl -w net.ipv4.tcp_keepalive_probes=4
sysctl -w net.ipv4.tcp_mtu_probing=1
ip link set dev wg0 mtu 1380 2>/dev/null || true
ss -s | grep -i estab
conntrack -C
```
`conntrack -C` покажет текущее число записей. Если оно больше половины `nf_conntrack_max` — срочно поднимайте лимит. `ss -s` покажет общее число established-сокетов. Больше 10 тысяч на слабом VPS — прокси не выдержит стрим.
Затем — тест из раздела про python. Держите сессию 40 минут. Если дожила — можно стримить. Если нет — крутите таймауты, пока не доживёт.
Что делать, если ничего не помогло
Бывает. Тогда вариантов немного. Первый — сменить протокол прокси на что-то с явным keepalive на уровне приложения, например Shadowsocks с `--keepalive 30`. Второй — использовать туннель на уровне L3 (WireGuard) вместо SOCKS. Тогда conntrack работает только на стороне клиента, а прокси видит чистый IP-трафик. Третий — стримить через RTMP напрямую, без TLS, если ingest-сервер это позволяет (некоторые регионы TikTok разрешают порт 1935 без шифрования).
И последнее: если стрим стабильно падает на 30-й минуте, а `conntrack` и таймауты в порядке — проверьте провайдера. Некоторые хостеры режут долгие TCP-сессии на уровне своего edge-роутера, независимо от ваших настроек. Это видно по `traceroute` — если разрыв на хопе провайдера, локальные sysctl не помогут. Меняйте хостера или требуйте от него `tcp_established_timeout` не меньше часа.