Чёрная дыра MTU: почему маленькие запросы работают, а загрузки застревают
Маленькие запросы завершаются, а большая загрузка висит вечно, и отправитель так и не узнаёт, что путь уже. Измерьте точный размер, на котором всё ломается.
Страница загружается, небольшой вызов API возвращается, а затем загрузка файла висит, пока клиент не сдастся. Ничего в журнале сервера этого не объясняет, потому что пакеты были отброшены посередине пути без полезного ответа. Этот шаблон — чёрная дыра MTU: канал уже, чем предполагает отправитель, плюс молчаливый отказ сказать об этом. Размер, на котором происходит сбой, воспроизводим, и именно это делает проблему диагностируемой.
Что делает дыру чёрной
У каждого канала есть максимальная единица передачи, и пакет больше ограничения следующего перехода должен быть либо фрагментирован, либо отвергнут сообщением ICMP, которое об этом говорит. Обнаружение MTU пути зависит от того, придёт ли это сообщение и будут ли его соблюдать. Фильтрация или игнорирование оставляет отправителя повторять пакет, который никогда не поместится.
Инкапсуляция делает это обычным вокруг VPN, потому что туннель добавляет внешний заголовок к пакету, уже рассчитанному под физический канал. Внутренний пакет допустим при 1500 байтах, а инкапсулированный нет, поэтому он погибает на границе, пока маленькие пакеты проходят. Завершённое рукопожатие и работающая страница входа доказывают только, что маленькие пакеты работают. Наибольший пакет, который приложение пытается отправить, и есть важный.
Найдите размер, на котором всё ломается
Двоичным поиском найдите размер полезной нагрузки тестом без фрагментации, от узла на одной стороне туннеля к узлу на другой. Наибольшая проходящая нагрузка — это ваш рабочий MTU пути минус накладные расходы заголовка.
ping -M do -s 1400 -c 2 192.0.2.20
ping -M do -s 1200 -c 2 192.0.2.20
ping -f -l 1372 192.0.2.20
Полезная нагрузка в 1472 байта проходит по чистому пути Ethernet в 1500 байт, как только добавляются 28 байт заголовков IPv4 и ICMP. Когда тот же тест проходит только при меньшей нагрузке после поднятия туннеля, разница — это накладные расходы инкапсуляции, а граница туннеля — то место, где должно было появиться сообщение об отказе. Запишите оба числа, до и после подключения, потому что эта пара и есть доказательство. Повторите тест для IPv6, когда приложение его использует, поскольку заголовок больше и порог отказа смещается.
Прочитайте доказательства, прежде чем винить туннель
Ошибки удалённых приложений, перегрузка и обычные потери тоже останавливают загрузки, поэтому одна неудачная передача не определяет чёрную дыру. Их разделяет соотношение размеров: воспроизводимый успех при 1200 байтах и воспроизводимая остановка при 1400 байтах, в одном направлении, через одну и ту же конечную точку.
Запись на обоих концах показывает, что большие пакеты уходят и никогда не приходят, без отказа между ними. Это момент посмотреть на пограничное устройство, а не на сервер, который отвечает на каждый фактически полученный запрос. Перегрузка тоже останавливает, но она останавливает разные размеры в разное время, и это другая подпись.
Проверьте исправление на обоих транспортах
Поддерживаемое исправление — позволить границе сообщать реальный предел или зажать там максимальный размер сегмента TCP, чтобы не предпринималась попытка слишком большого сегмента. Понижение каждого MTU в сети или отключение межсетевого экрана скрывает симптом и оставляет конфигурацию, о которой позже никто не сможет рассуждать.
ip link show dev tun0
sudo nft list ruleset | grep -i -E 'mtu|mss'
sysctl net.ipv4.tcp_mtu_probing
ping -M do -s 1400 -c 2 192.0.2.20
Проверьте тем же тестом размера, затем приложением, которое не сработало, и проверьте второй протокол, потому что приложения UDP никогда не получают коррекцию размера сегмента TCP. Сохраните исходные значения в заявке, потому что исправление, которое нельзя отменить, — это вторая проблема, ждущая своего часа. Если тест размера проходит, а загрузка всё ещё висит, чёрная дыра не была причиной, и расследование должно продолжиться, а не расширять изменение.