🦎 Как читать pcap видеосоединения с узлом googlevideo: попадание в кэш, потери и поддельные пакеты Zapret

О чём заметка
Практический разбор четырёх записей трафика (pcap) к одному кэшу Google в сети ТТК, выложенных на форуме ntc.party 17 октября 2026 года, на примере сбоя YouTube у абонентов ТТК. Показано, как по записи отличить ответ из кэша от ответа с долгой паузой, как найти потери пакетов, почему в начале соединения встречаются огромные номера
Seq, и что именно при этом делает обход. В конце приведён скрипт на стандартной библиотеке Python, которым можно проверить собственную запись.
TL;DR
- Каждая из четырёх записей содержит одно TCP-соединение с узлом
188.43.65.140:443: рукопожатие SYN → SYN-ACK занимает 5–6 мс. На запросы видеофрагментов узел отвечал за 7–42 мс, но один запрос из восьми ждал первого байта 2,9 с, после чего ролик шёл со скоростью 0,5–1,6 Мбит/с. - Повторно узел отправил от 5 до 21% байтов; больше всего повторов пришлось на самый быстрый всплеск (693 КБ при 2 620 КБ новых в первую секунду записи 63), а на медленном участке их доля около 5%. Поэтому скорость 0,5–1,6 Мбит/с объясняют не потери, а то, что данные узел получал порциями примерно раз в секунду.
- Огромные номера
Seqвроде4294957297— поддельные пакеты обхода (смещение −10 000, сто байт). Они создают дубликаты подтверждений и повторные отправки в рукопожатии, и из-за них первый запрос после рукопожатия уходит с задержкой около 0,3 с. Это цена обхода, а не признак поломки узла. - В трёх записях соединение закрывает клиент (
FIN), а узел отвечаетRSTчерез 4–7 мс. Это не обрыв со стороны узла.
Что именно снято
Записи сделаны на роутере: адрес клиента 192.168.4.20 из локальной сети, адрес узла 188.43.65.140. Различие в TTL (оставшемся числе переходов пакета) помогает понять, где снималось: у пакетов клиента TTL 63, то есть пакет уже прошёл один маршрутизатор, а у пакетов узла 116 (если исходное значение 128, то это 12 переходов). Все пакеты узла во всех четырёх записях, 951, 1 929, 130 и 120 штук, несут один и тот же TTL 116. Подмешанных пакетов с другим TTL, которые часто выдают вмешательство стороннего устройства, среди них нет. Это слабый довод: устройство, стоящее на том же расстоянии, даст тот же TTL. Но он показывает, что сброс в конце соединения пришёл оттуда же, откуда и данные.
| Запись | Длительность | Пакетов от узла | Данных от узла | Конец соединения |
|---|---|---|---|---|
| Retransmission25 | 3,2 с | 120 | 0,63 МБ | запись обрывается |
| Retransmission63 | 46,7 с | 951 | 3,18 МБ | клиент FIN в 46,721 с, узел RST в 46,725 с |
| Retransmission64 | 22,0 с | 1 929 | 6,48 МБ | клиент FIN в 21,947 с, узел RST в 21,954 с |
| Retransmission82 | 7,9 с | 130 | 0,54 МБ | клиент FIN в 7,895 с, узел RST в 7,902 с |
В трёх записях RST приходит через 4–7 мс после FIN клиента. Соединение закрыл сам браузер (вкладка, смена ролика или тайм-аут плеера), а узел закрыл его сбросом вместо привычного FIN. Для жалоб на ERR_CONNECTION_RESET этот факт не годится как объяснение: сброс здесь следствие закрытия клиентом.
Что показывает скриншот Wireshark
Автор записей приложил и скриншот начала одного из соединений в Wireshark. Лучше всего разобрать его по строкам: именно на этом кадре виден механизм, который сбил с толку автора.

На кадре 56 строк (с номера 48511 по 48566) за 0,58 секунды. Строки 48511–48513 — обычное рукопожатие: клиент шлёт SYN, узел отвечает SYN, ACK через 5 мс, клиент подтверждает. Дальше сразу видна необычная картина: между настоящими пакетами идут строки [TCP Spurious Retransmission] с Seq=4294957297, длиной 100 байт и TTL 63. Wireshark подписывает как «ложную повторную передачу» любой сегмент, чей номер лежит далеко позади уже подтверждённого. Вместе с ними идут [TCP Dup ACK 48512#1] от узла (подтверждение одного и того же номера много раз подряд), [TCP Previous segment not captured], [TCP Fast Retransmission] и Client Hello (SNI…. В строке 48532 появляется Server Hello, Change Cipher Spec от узла, в 48533 Encrypted Extensions, Certificate, а на 48566 уже запрос POST /videoplayback?expi…: Wireshark показывает в этой строке расшифрованный запрос.
Главное в кадре то, что поддельные пакеты исходят от клиента, а не от узла. В колонке «Source» у них стоит адрес 192.168.4.20, а число 4294957297 равно 2³² − 9999: это номер 1 минус 10 000 по модулю 2³². Такой сдвиг — badseq с принятым в zapret1 смещением −10 000 (подробнее в заметке ts и fooling). Настоящий сервер такой пакет отбрасывает, а устройство фильтрации, которое не следит за окном, может принять за часть запроса. В записях пять, пять, три и пять таких пакетов, сто байт каждый.
Хронометраж рукопожатия: цена обхода
Лог первых 0,6 секунды записи 25 (колонки: время, направление, флаги, номер относительно начала потока, длина). Строки с длиной 100 и номером около −10 000 — поддельные пакеты, остальные — настоящие.
0.000 C>S S seq=0 len=0 ack=0
0.005 S>C SA seq=0 len=0 ack=1 ← узел ответил за 5 мс
0.006 C>S A seq=1 len=0 ack=1
0.104 C>S A seq=-9999 len=100 ack=1 ← поддельный пакет (seq 1−10000)
0.104 C>S A seq=1 len=1 ack=1 ← первый байт ClientHello отдельно
0.108 S>C A seq=1 len=0 ack=1 ← дубликат подтверждения от узла
0.108 S>C A seq=1 len=0 ack=2
0.139 C>S AP seq=2 len=1399 ack=1 ← остальные 1399 байт
0.144 S>C A seq=1 len=0 ack=1401 ← узел подтвердил первые 1400 байт
0.200 C>S AP seq=-8599 len=100 ack=1 ← следующий поддельный
0.200 C>S A seq=1750 len=10 ack=1 ← мелкие куски в обратном порядке
0.239 C>S A seq=1402 len=326 ack=1
0.251 C>S A seq=1401 len=1 ack=1 ← «дырка» закрыта лишь через 0,25 с
0.261 S>C A seq=1 len=2800 ack=1857 ← узел получил весь ClientHello и отвечаетВидно три вещи. ClientHello (1856 байт) отправляется кусками в обратном порядке: сначала хвост, потом начало, а единственный байт со смещением 1401 уходит последним, пока он не дойдёт, узел не может подтвердить пришедшие ранее куски. Ответ (ServerHello) начинается в 0,261 с вместо примерно 0,015 с, которые заняло бы обычное рукопожатие на линии с задержкой 5 мс. Затем всё повторяется на запросе: после рукопожатия клиент отправляет свой первый запрос (около 3,6 КБ), и обход режет его так же, с поддельным пакетом перед каждым куском.
0.394 C>S A seq=-8143 len=100 ack=4480 ← поддельный перед запросом
0.394 C>S A seq=1857 len=1 ack=4480 ← 1 байт запроса
0.435 C>S AP seq=1858 len=1399 ack=4480
0.493 C>S AP seq=-6743 len=100 ack=4480 ← поддельный перед вторым куском
0.493 C>S A seq=3257 len=1 ack=4480
0.529 C>S AP seq=3258 len=1399 ack=4480
0.593 C>S AP seq=-5343 len=100 ack=4480
0.593 C>S AP seq=4658 len=834 ack=4480 ← запрос дослан только в 0,59 сЗапрос, который мог уйти одним пакетом сразу после рукопожатия, достигает узла только через 0,6 с от начала соединения. Это не причина того, что видео идёт медленно после первых секунд, а лишняя стоимость каждого нового соединения: чем больше соединений открывает плеер, тем больше её платят. Проверить, нужен ли обходу этот приём именно для адресов кэша внутри сети провайдера, можно экспериментом: ограничить число обрабатываемых пакетов соединения (у winws это --dpi-desync-cutoff) и посмотреть, изменится ли время до первых данных. Пока такой замер не сделан ни автором записей, ни в обсуждении, поэтому это гипотеза, а не рекомендация.
Запросы и ответы: быстрый ответ и пауза
Чтобы отличить ответ из кэша от ответа с долгой паузой, в каждой записи найдены запросы клиента с телом около 3,5–3,8 КБ и измерено, через сколько миллисекунд после последнего пакета запроса пришёл первый пакет данных. Расчёт сделан скриптом из конца заметки.
| Запись | Момент запроса | Первые данные через | Что пришло до следующего запроса |
|---|---|---|---|
| 63 | 0,764 с | 42 мс | 2,09 МБ |
| 63 | 0,974 с | 7 мс | 1 КБ |
| 63 | 31,849 с | 2 861 мс | 0,56 МБ |
| 64 | 6,185 с | 12 мс | 2,74 МБ |
| 64 | 8,479 с | 9 мс | 1,35 МБ |
| 64 | 10,547 с | 9 мс | 0,80 МБ |
| 64 | 12,672 с | 8 мс | 1,50 МБ |
| 64 | 16,923 с | 9 мс | 0,09 МБ |

Из восьми запросов семь получили первый ответ за 7–42 мс, что совпадает с задержкой линии: узел отвечал сразу. Один (запись 63, 31,849 с) ждал 2,86 секунды. Лог этого запроса показывает, что в паузу узел не молчал на уровне TCP: подтверждения пришли через 5 мс, и только данные задержались.
31.849 C>S AP seq=12765 len=1857 ack=3281355
31.849 C>S AP seq=14622 len=1954 ack=3281355 ← запрос (3811 байт) отправлен
31.854 S>C A seq=3281355 len=0 ack=14622 ← узел подтвердил первую часть через 5 мс
31.855 S>C A seq=3281355 len=0 ack=16576 ← и весь запрос
34.710 S>C AP seq=3281355 len=1442 ack=16576 ← первые данные через 2,86 с
34.710 S>C AP seq=3282797 len=5600 ack=16576 ← дальше 117 КБ с одной отметкой времени
35.324 S>C AP seq=3397955 len=5600 ack=16576 ← следующая порция через 0,6 сСопоставьте с запросом из записи 64: он ушёл в 6,185 с, ответ начался в 6,196 с, и за первые полсекунды узел отдал около 1,4 МБ.
6.185 C>S AP seq=2777 len=1856 ack=5162
6.185 C>S AP seq=4633 len=1893 ack=5162 ← запрос
6.193 S>C A seq=5162 len=0 ack=6526 ← подтверждение за 8 мс
6.196 S>C AP seq=5162 len=1442 ack=6526 ← первые данные через 11–12 мс
6.196 S>C AP seq=8004 len=4200 ack=6526
6.201 S>C A seq=12204 len=2800 ack=6526 ← дальше с интервалами 5 мсОтвет на «горячий» запрос идёт сразу, ровно такой, каким бывает ответ из кэша. Запрос с паузой в 2,86 секунды при том же быстром подтверждении TCP выглядит как запрос, для которого узлу пришлось ждать данные откуда-то ещё. Затем данные приходят порциями (с отметок 34,71, 35,32, 36,20, 37,17, 38,19 с), то есть примерно раз в секунду, по 60–200 КБ, что даёт 0,5–1,6 Мбит/с. Так ведёт себя узел, который получает ролик из внешнего источника и передаёт его дальше по мере поступления. Другие объяснения возможны (очередь или ограничитель скорости внутри сети), но пауза до первого байта при мгновенных подтверждениях TCP для них менее естественна, чем для версии с докачкой; доказательством это не служит.
Повторные передачи: где они встречаются
Повторная передача — это когда узел отправляет сегмент ещё раз, потому что клиент не подтвердил его получение. Её доля показывает, сколько данных теряется по дороге. Подсчёт сделан по правилу «номер сегмента меньше уже отправленного».

В записи 63 в первую секунду узел отдал 2 620 КБ и ещё 693 КБ отправил повторно: доля 20,9%. В записи 64 повторно ушло 357 КБ из 6 836 КБ, это 5,2%, и повторы сосредоточены в четырёх порциях по 1–1,5 МБ (130, 91, 51 и 84 КБ). На медленном участке записи 63 после 34-й секунды повторных всего 28 КБ из 584 КБ, доля около 5%. Закономерность читается так: потери возникают в быстрых всплесках, когда узел отдаёт сотни килобайт подряд, и их почти нет в медленной фазе. Такая картина согласуется с очередью или ограничителем скорости с небольшим буфером на пути, который отбрасывает хвост всплеска. Но она же показывает, что низкая скорость на медленном участке объясняется не потерями, а скоростью источника: потери там лишь около 5%, а скорость в десятки раз ниже, чем в быстрых всплесках.
В записи нет эталона: нет второй записи того же узла при том же ролике без обхода, и нет записи узла, у которого всё в порядке. Поэтому цифры в 5% и 21% нельзя сравнить с нормой, а лишь с самими собой.
Что можно утверждать, а что нет
По записям можно утверждать: рукопожатие проходит; узел отвечает на запросы за единицы-десятки миллисекунд, пока ему нечего ждать; один запрос ждал почти три секунды при быстрых подтверждениях TCP; затем данные шли порциями раз в секунду; потери сосредоточены в быстрых всплесках; обход добавляет в начале соединения поддельные пакеты и около полусекунды задержки. По записям нельзя утверждать, что виноват именно кэш, провайдер или фильтрация: на стороне записи видна только одна половина пути. Чтобы разделить версии, нужны замеры из других точек: тот же запрос из сети ТТК и из сети вне ТТК, запись без обхода, запись при роликах, заведомо лежащих в кэше и заведомо отсутствующих в нём.
Скрипт для проверки собственной записи
Скрипт читает pcap (Ethernet, IPv4), берёт самое длинное TCP-соединение и печатает то же, что в таблицах выше. Нужен только Python 3, ничего устанавливать не надо.
#!/usr/bin/env python3
# Разбор одного TCP-соединения с узлом googlevideo из pcap (Ethernet/IPv4), только стандартная библиотека.
# Запуск: python3 -I pcap_gv.py файл.pcap
import struct, sys, socket
def packets(path):
d = open(path, 'rb').read()
end = '<' if d[:4] == b'\xd4\xc3\xb2\xa1' else '>'
o = 24
while o + 16 <= len(d):
s, us, cap, _ = struct.unpack(end + 'IIII', d[o:o + 16]); o += 16
yield s + us / 1e6, d[o:o + cap]; o += cap
def s32(x):
x &= 0xffffffff
return x - (1 << 32) if x >> 31 else x
def main(path):
pk = []
for ts, f in packets(path):
if len(f) < 54 or f[12:14] != b'\x08\x00' or f[23] != 6: continue
ihl = (f[14] & 15) * 4; ip = f[14:]; tcp = ip[ihl:struct.unpack('!H', ip[2:4])[0]]
sp, dp, seq, ack = struct.unpack('!HHII', tcp[:12])
pk.append((ts, socket.inet_ntoa(ip[12:16]), sp, seq, tcp[13], len(tcp) - (tcp[12] >> 4) * 4))
t0 = pk[0][0]; cli = (pk[0][1], pk[0][2])
cmax = smax = synack = None
req, srv, fake, retr = [], [], [], 0
for ts, ip, sp, seq, fl, n in pk:
t = ts - t0; c = (ip, sp) == cli
if fl & 18 == 18 and synack is None: synack = t
if n == 0: continue
if c:
if cmax is None: cmax = seq + n; req.append((t, n)); continue
d = s32(seq - cmax)
if d < -5000: fake.append(round(t, 2)) # поддельные: номер сильно позади
elif d >= 0: cmax = seq + n; req.append((t, n))
else:
if smax is None: smax = seq + n; srv.append((t, n)); continue
d = s32(seq - smax)
if d >= 0: smax = seq + n; srv.append((t, n))
else: retr += n # повторная передача узла
print('SYN→SYN-ACK: %.1f мс' % (synack * 1000)); print('поддельные пакеты клиента, с:', fake)
grp = []
for t, n in req: # пачка отправок в пределах 50 мс = один запрос
if grp and t - grp[-1][2] < .05: grp[-1][1] += n; grp[-1][2] = t
else: grp.append([t, n, t])
for t, n, last in grp:
first = next((ts for ts, _ in srv if ts > last), None)
if n > 3500 and first: print('запрос в %7.3f с, %5d Б → первые данные через %6.0f мс' % (t, n, (first - last) * 1000))
tot = sum(n for _, n in srv)
print('данных от узла %d КБ, повторно %d КБ (%.1f%%)' % (tot // 1000, retr // 1000, 100 * retr / (tot + retr)))
main(sys.argv[1])Вывод скрипта для записи 63 совпадает с таблицей выше: SYN→SYN-ACK: 4.9 мс, пять поддельных пакетов в первые 0,8 секунды, три запроса с задержками 42, 7 и 2 861 мс и итог данных от узла 3180 КБ, повторно 724 КБ (18.5%). Скрипт не пытается разбирать TLS и не расшифровывает запросы: тело запроса определяется только по длине.
Источники
- Issue № 18162 у Flowseal, «Ютуб сильно заболел», где обсуждается этот сбой: https://github.com/Flowseal/zapret-discord-youtube/issues/18162
- Форум ntc.party, тема от 17 октября 2026 об отдаче YouTube через разные кэши
googlevideo.com, откуда взяты четыре pcap и скриншот Wireshark: прямой ссылки нет, форум недоступен с сервера, который собирает вики - Собственный разбор записей
Retransmission25,Retransmission63,Retransmission64иRetransmission82скриптом из этой заметки
📚 См. также
- ТТК и YouTube: кэши Google в сети Транстелекома (с 17 октября 2026) — инцидент и версии причины
- Кэши Google (GGC) у провайдера — как YouTube выбирает узел и что показали прямые замеры
- ts и fooling — почему обход добавляет поддельные пакеты и зачем смещение −10 000
- Как проверить стратегию — как отличать работающую стратегию от случайного успеха
🤖 Эти статьи открыты — можно обучать на них ИИ
При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование доступно в Forgejo: исходник этой заметки · скачать весь репозиторий одним zip-архивом.