TikTok через IPv6 прокси: почему автопостинг срывается на этапе загрузки видео и как это чинится
Содержание
- Почему именно загрузка видео — самое слабое место
- Что реально ломается: три сценария
- Как выглядит срыв в логах
- Настройка MTU и PMTUD на прокси
- Почему ротация IPv6-адресов убивает загрузку
- Заголовки и TLS-фингерпринт
- Кейс: nginx-прокси с дефолтными буферами
- Кейс: ротация адреса в середине upload
- Кейс: обрыв на Expect: 100-continue
- Мониторинг: что мерить
- Чеклист перед запуском автопостера
- Что делать, если ничего не помогло
Автопостинг в TikTok через прокси — задача с подводными камнями. Настройка выглядит рабочей: запросы проходят, токены получаются, профиль читается. А потом наступает момент загрузки видео — и всё разваливается. Разбираем, почему загрузка видео через IPv6 прокси срывается чаще, чем обычные API-вызовы, и что с этим делать.
Почему именно загрузка видео — самое слабое место
Любой автопостер делает три вещи: авторизуется, готовит метаданные, заливает файл. Первые два шага — это лёгкие JSON-запросы по 2-5 КБ. Загрузка видео — это multipart upload на десятки мегабайт, который держит TCP-соединение открытым минуты.
IPv6 здесь добавляет свой слой проблем. MTU у IPv6 минимально 1280 байт против 576 у IPv4. PMTUD (Path MTU Discovery) в IPv6 обязателен, и если ICMPv6-сообщения «Packet Too Big» где-то режутся — соединение зависает на передаче крупных пакетов. Мелкие запросы проскакивают, большой файл — нет.
Плюс сам TikTok относится к загрузке видео иначе, чем к чтению ленты. Серверная сторона проверяет consistency: IP-адрес, TLS-фингерпринт, заголовки, тайминги. Если между запросом на upload-init и фактической передачей байтов IP поменялся или соединение выглядит подозрительно — загрузка отбивается.
Что реально ломается: три сценария
Первый — таймаут на этапе `upload/init`. Клиент получает upload URL, начинает заливать, а соединение рвётся через 30-60 секунд. Причина чаще всего в том, что прокси-сервер закрывает долгоживущие соединения по idle-таймауту, не учитывая, что клиент ещё передаёт данные.
Второй — рассинхрон IPv6-адресов. Прокси пул ротирует адреса, и upload-init уходит с одного адреса, а чанки файла — с другого. TikTok видит несоответствие и возвращает 403 или молча обрывает соединение.
Третий — фрагментация. Если на пути стоит устройство с MTU меньше 1280 (редко, но бывает в туннелях), IPv6-пакеты дропаются без ICMPv6. Клиент ждёт, ретрансмитит, в итоге таймаут.
Как выглядит срыв в логах
Типичная картина при провале загрузки через curl:
```bash
curl -6 -v --max-time 120 \
-X POST "https://api16-normal-c-useast1a.tiktokv.com/aweme/v1/upload/video/" \
-H "Content-Type: multipart/form-data" \
-F "video=@clip.mp4" \
--interface 2001:db8::1
```
В выводе видно `* Trying 2001:db8::1...`, потом `* Connected`, затем зависание на `> Expect: 100-continue`. Сервер не отвечает `100 Continue` вовремя — curl ждёт секунду по умолчанию и начинает слать тело, но соединение уже в подвешенном состоянии. Через 120 секунд — `curl: (28) Operation timed out`.
Лечится отключением Expect и явным управлением размером чанка:
```bash
curl -6 --max-time 300 \
-H "Expect:" \
-H "Transfer-Encoding: chunked" \
-X POST "https://api16-normal-c-useast1a.tiktokv.com/aweme/v1/upload/video/" \
-F "video=@clip.mp4;type=video/mp4" \
--interface 2001:db8::1
```
Настройка MTU и PMTUD на прокси
Первое, что проверяем на прокси-сервере — MTU на интерфейсе. Для IPv6-туннелей часто ставят 1480 или 1420, но забывают про клиентскую сторону. Если клиент шлёт пакеты по 1500, а туннель держит 1420 — нужен рабочий PMTUD.
Проверка:
```bash
ping6 -M do -s 1400 -c 3 api16-normal-c-useast1a.tiktokv.com
```
Если `-s 1400` проходит, а `-s 1452` — нет, значит MTU на пути 1420-1440. На прокси фиксируем:
```bash
ip link set dev eth0 mtu 1420
sysctl -w net.ipv6.conf.eth0.accept_ra_mtu=1
sysctl -w net.ipv6.conf.all.mtu_expires=600
```
`accept_ra_mtu=1` позволяет ядру принимать MTU из Router Advertisement. Без этого параметра интерфейс может держать дефолтные 1500 и игнорировать реальный MTU пути.
Почему ротация IPv6-адресов убивает загрузку
Пул IPv6 — это тысячи адресов из одной /64 или /48. Соблазн ротировать адрес на каждый запрос велик. Для лёгких API-вызовов это работает. Для загрузки видео — нет.
Механика: upload-init возвращает upload_id и набор URL для чанков. Сервер привязывает upload_id к IP, с которого пришёл init. Следующий чанк с другим адресом — отказ. Логика простая: защита от подмены клиента в процессе загрузки.
Решение — sticky-сессия на время загрузки. Один файл = один IPv6-адрес от init до последнего чанка. В коде это выглядит как привязка адреса к объекту сессии:
```python
import requests
import ipaddress
class TikTokUploader:
def __init__(self, proxy_pool):
self.pool = proxy_pool
self.session_ip = None
def _pick_ip(self):
if self.session_ip is None:
self.session_ip = self.pool.next_ipv6()
return self.session_ip
def upload(self, filepath, metadata):
ip = self._pick_ip()
proxies = {
"http": f"http://[{ip}]:3128",
"https": f"http://[{ip}]:3128",
}
try:
with open(filepath, "rb") as f:
r = requests.post(
"https://api16-normal-c-useast1a.tiktokv.com/aweme/v1/upload/video/",
files={"video": ("clip.mp4", f, "video/mp4")},
data=metadata,
proxies=proxies,
timeout=(10, 300),
)
return r.status_code, r.text
finally:
self.session_ip = None
```
Адрес освобождается только после завершения загрузки. Между файлами — можно менять. Внутри одного файла — нельзя.
Заголовки и TLS-фингерпринт
TikTok проверяет не только IP. TLS-отпечаток клиента, набор заголовков, порядок их передачи — всё это уходит на серверную проверку. Загрузка видео — самый «дорогой» запрос с точки зрения антифрода, поэтому проверки там строже.
Если используешь `requests` из Python — TLS-фингерпринт будет характерным для OpenSSL, который стоит под капотом. Это палится. Для загрузки лучше брать curl-impersonate или httpx с кастомным SSL-контекстом.
Заголовки для загрузки должны совпадать с теми, что уходили при авторизации. User-Agent, X-Request-Id, Cookie — единые в рамках сессии. Расхождение между upload-init и загрузкой чанков — красный флаг.
Кейс: nginx-прокси с дефолтными буферами
Сервер на nginx 1.24, настроен как forward-proxy для IPv6-клиентов. Автопостер на Python заливает видео по 40-80 МБ. Загрузка обрывается на 8-12 МБ стабильно.
Причина: `client_max_body_size` по умолчанию 1 МБ, но в forward-proxy это не работает так же, как в reverse. Проблема оказалась в `proxy_request_buffering on` — nginx буферизует весь запрос на диск перед отправкой апстриму. При 80 МБ и медленном диске буфер переполняется, соединение рвётся по таймауту.
Решение — отключить буферизацию и поднять таймауты:
```nginx
proxy_request_buffering off;
proxy_buffering off;
proxy_send_timeout 600s;
proxy_read_timeout 600s;
client_body_timeout 600s;
client_max_body_size 200m;
```
После правки загрузка 80 МБ прошла за 47 секунд без обрывов. До правки — стабильный обрыв на 12-15 секунде.
Кейс: ротация адреса в середине upload
Пул на 5000 IPv6-адресов, ротация каждые 30 секунд по таймеру. Автопостер заливал видео 60-90 секунд. На 31-й секунде адрес менялся — TikTok возвращал 403 с телом `{"status_code": 10013, "message": "invalid upload session"}`.
Причина — та самая привязка upload_id к IP. Ротация по таймеру не учитывает активные сессии. Фикс: ротация только по завершении сессии, плюс sticky-таймаут 5 минут на адрес после последнего запроса.
Цифры до и после: успешных загрузок было 34% от попыток, стало 96%. Среднее время загрузки 50-МБ файла — 38 секунд, дисперсия ±6 секунд.
Кейс: обрыв на Expect: 100-continue
Клиент на Go, загрузка через `http.Client` с IPv6-прокси. Файлы до 20 МБ проходили, больше — таймаут на 60 секунд. В логах прокси — соединение открыто, данные не идут.
Причина: Go-клиент шлёт `Expect: 100-continue` для тел больше 1 МБ. Прокси-сервер (Squid 5.7) не пробрасывает `100 Continue` от апстрима корректно для IPv6-соединений — известный баг в некоторых сборках. Клиент ждёт подтверждения, сервер ждёт тело. Взаимная блокировка до таймаута.
Решение — отключить Expect на клиенте:
```go
req.Header.Set("Expect", "")
transport := &http.Transport{
Proxy: http.ProxyURL(proxyURL),
ExpectContinueTimeout: 1 * time.Second,
DisableCompression: true,
}
```
Или обновить Squid до 6.x, где поведение исправлено. После отключения Expect загрузка 20 МБ пошла за 14 секунд вместо таймаута.
Мониторинг: что мерить
Без метрик срыв загрузки выглядит как «иногда не работает». Мерить нужно конкретное:
| Метрика | Норма | Тревога |
|---|---|---|
| Время до первого байта ответа | < 800 мс | > 3000 мс |
| Скорость загрузки | > 1.5 МБ/с | < 300 КБ/с |
| Доля успешных upload | > 90% | < 70% |
| Таймауты на 100 файлов | 0-2 | > 10 |
Логировать нужно код ответа, время каждого этапа (init, передача, финализация), IPv6-адрес сессии. Без привязки адреса к логу разбор инцидента превращается в гадание.
Чеклист перед запуском автопостера
Проверить MTU на всём пути от клиента до прокси. Убедиться, что PMTUD работает и ICMPv6 не режется. Настроить sticky-сессию по IP на время загрузки одного файла. Отключить `Expect: 100-continue` или убедиться, что прокси его корректно пробрасывает. Синхронизировать заголовки и TLS-фингерпринт между init и загрузкой. Поднять таймауты на прокси до 300-600 секунд. Проверить, что `proxy_request_buffering` выключен, если используешь nginx. Логировать IPv6-адрес для каждой загрузки.
Прокси-сервис вроде lexic.ml даёт пул IPv6 с 2015 года, и там есть возможность фиксировать адрес на сессию — это как раз то, что нужно для стабильной загрузки видео. Без sticky-режима любой пул превращается в лотерею.
Что делать, если ничего не помогло
Иногда проблема не в прокси, а в самом файле. TikTok отклоняет видео с нестандартным кодеком, битрейтом выше 8 Мбит/с, разрешением вне 540x960 — 1080x1920. Проверяй через ffprobe перед загрузкой:
```bash
ffprobe -v error -select_streams v:0 \
-show_entries stream=codec_name,bit_rate,width,height \
-of csv=p=0 clip.mp4
```
Если кодек h265 — конвертируй в h264. Если битрейт 12 Мбит/с — пережми до 6-8. Это не про прокси, но срыв на этапе загрузки часто путают с сетевыми проблемами, а на деле сервер отбивает файл по валидации и возвращает невнятную ошибку.
Второй момент — размер чанка. Некоторые реализации режут файл на куски по 5 МБ. Для IPv6-соединений с потерями лучше 2-3 МБ: меньше вероятность, что один потерянный пакет уронит весь чанк и вызовет ретрансмит всего куска.
Третий — параллельные загрузки. Если автопостер льёт 10 видео одновременно через один прокси-пул, адреса конфликтуют, sticky-сессии пересекаются. Ограничивай конкурентность до 3-5 на пул, остальное — в очередь.