MTU, MSS и фрагментация: почему сайт «открывается через раз»
Ping идёт, SSH работает, главная открывается — а страница с картинками висит и отваливается по таймауту. Через VPN не грузится половина сайтов, при отключённом VPN всё в порядке. Это не «интернет барахлит», а почти всегда одна конкретная неисправность: слишком большие пакеты и потерянное сообщение о том, что они слишком большие.
MTU — максимальный размер пакета в байтах, который можно передать по каналу за один раз, без разбиения. MSS — производная величина: сколько из этого размера остаётся под полезные данные TCP после вычета заголовков. Для обычного Ethernet MTU равен 1500 байт, а MSS — 1460.
Стандартные значения MTU
Ethernet задаёт 1500 байт, и от этого числа считается всё остальное. Два значения в таблице — не рекомендации, а требования стандартов: пакет меньше минимального просто не пройдёт.
| Среда | MTU, байт | Откуда |
|---|---|---|
| Ethernet | 1500 | Стандарт IEEE 802.3 |
| PPPoE (домашний ADSL, часть провайдеров) | 1492 | RFC 2516: 6 байт PPPoE + 2 байта PPP |
| Jumbo frames | 9000 | Де-факто, в стандарте не закреплено |
| Минимум для IPv4 | 68 | RFC 791 |
| Минимум для IPv6 | 1280 | RFC 8200, жёсткое требование |
Сколько откусывает каждый туннель
Любой туннель заворачивает ваш пакет в дополнительные заголовки, и MTU внутри туннеля становится меньше. Отсюда правило: проблемы с MTU почти всегда начинаются в тот день, когда в схеме появился VPN.
| Туннель | Оверхед | MTU от 1500 | MSS |
|---|---|---|---|
| PPPoE | 8 | 1492 | 1452 |
| GRE over IPv4 | 24 | 1476 | 1436 |
| L2TP поверх UDP | 40 | 1460 | 1420 |
| VXLAN over IPv4 | 50 | 1450 | 1410 |
| IPsec ESP, tunnel mode | 60 | 1440 | 1400 |
| WireGuard поверх IPv4 | 60 | 1440 | 1400 |
| WireGuard, значение по умолчанию | — | 1420 | 1380 |
| OpenVPN поверх UDP, внешний IPv6 | 88 | 1412 | 1372 |
| GRE поверх IPsec | 84 | 1416 | 1376 |
| OpenVPN поверх TCP, внешний IPv6 | 140 | 1360 | 1320 |
WireGuard ставит 1420 по умолчанию не случайно: это консервативное значение, которое покрывает и вариант с IPv6 снаружи, где оверхед составляет 80 байт.
Как из MTU получается MSS
MSS — это MTU минус заголовки IP и TCP. Формула разная для двух версий протокола, потому что заголовок IPv6 вдвое длиннее.
IPv4: MSS = MTU - 20 (IP) - 20 (TCP) = MTU - 40
1500 - 40 = 1460
IPv6: MSS = MTU - 40 (IPv6) - 20 (TCP) = MTU - 60
1500 - 60 = 1440
Эти же числа зашиты в ядро: опция --clamp-mss-to-pmtu в iptables вычитает ровно 40 байт для IPv4 и 60 для IPv6. MSS стороны сообщают друг другу один раз, в SYN-пакете при установке соединения, и дальше не пересматривают.
Что происходит, когда пакет не влезает
Сценариев ровно три, и именно третий даёт симптом «сайт открывается через раз».
| Ситуация | Что делает маршрутизатор | Итог |
|---|---|---|
| IPv4, бит DF не стоит | Режет пакет на фрагменты | Работает, но медленно |
| IPv4, бит DF стоит, ICMP проходит | Отбрасывает и шлёт ICMP «нужна фрагментация» | Отправитель уменьшает пакет, всё работает |
| IPv4, DF стоит, ICMP режется файрволом | Отбрасывает молча | Чёрная дыра MTU |
| IPv6, любой случай | Фрагментировать не имеет права вообще | Только Packet Too Big или потеря |
Сообщение, которое всё чинит, называется по-разному в двух версиях протокола:
| Протокол | Сообщение | Тип и код |
|---|---|---|
| IPv4 | Fragmentation Needed and DF was Set | ICMP type 3, code 4 |
| IPv6 | Packet Too Big | ICMPv6 type 2, code 0 |
RFC 8900 формулирует прямо: операторы не должны фильтровать сообщения ICMPv6 Packet Too Big, если не доказано, что они подделаны, — фильтрация этих пакетов ломает определение MTU пути. Ровно то же касается ICMP type 3 code 4 в IPv4. Запрещать нужно echo-запросы, если очень хочется, а не весь протокол целиком.
Чёрная дыра MTU: как она выглядит со стороны пользователя
Симптоматика очень характерная, и по ней диагноз ставится почти без инструментов.
| Что происходит | Почему |
|---|---|
| Ping проходит | Echo-запрос — 84 байта, влезает куда угодно |
| TCP-соединение устанавливается | SYN и SYN-ACK маленькие |
| SSH работает, интерактив летает | Нажатия клавиш — десятки байт |
| scp или git clone зависают на 0% | Первый же полноразмерный пакет пропадает |
| Главная открылась, страница с картинками — нет | Маленький HTML прошёл, большой ответ — нет |
| Сайт открывается у части пользователей | У них другой провайдер и другой путь |
Причины, по которым сообщение не доходит: файрвол или ACL режут весь ICMP; NAT или точка терминации туннеля не генерируют ответ; асимметричная маршрутизация и anycast — сообщение уходит не тому узлу; rate-limit на ICMP у оператора.
Как измерить реальный MTU пути
Метод один: отправлять ping с запретом фрагментации и увеличивать размер, пока пакеты не начнут пропадать. Ключевая деталь, на которой ошибаются все: размер в команде — это полезная нагрузка ICMP, без заголовков. К результату нужно прибавить 28 байт для IPv4 (20 IP + 8 ICMP) и 48 байт для IPv6.
# Linux: -M do запрещает фрагментацию, -s задаёт нагрузку ping -M do -s 1472 -c 3 8.8.8.8 # 1472 + 28 = 1500 ping -M do -s 1464 -c 3 8.8.8.8 # 1464 + 28 = 1492, типичный PPPoE ping -M do -s 1392 -c 3 8.8.8.8 # 1392 + 28 = 1420, WireGuard # при превышении: Frag needed and DF set (mtu = 1492) # Windows: -f запрещает фрагментацию, -l задаёт нагрузку ping -f -l 1472 -n 3 8.8.8.8 # при превышении: Packet needs to be fragmented but DF set. netsh interface ipv4 show subinterfaces # посмотреть MTU интерфейсов # macOS и FreeBSD: -D вместо -M do ping -D -s 1472 -c 3 8.8.8.8
| Прошёл размер | MTU пути | На что похоже |
|---|---|---|
| 1472 | 1500 | Чистый Ethernet, всё в порядке |
| 1464 | 1492 | PPPoE у провайдера |
| 1448 | 1476 | GRE-туннель на пути |
| 1412 | 1440 | IPsec или WireGuard |
| 1392 | 1420 | WireGuard с дефолтными настройками |
| Меньше 1272 | меньше 1300 | Два туннеля подряд или что-то сломано |
Искать нужно бинарным поиском: 1472 не прошло — пробуем 1400, прошло — пробуем 1436, и так далее. Пяти-шести итераций хватает, чтобы найти точное значение.
В macOS 15 Sequoia зафиксирован баг: ping -D не выставляет бит DF при включённом SIP, хотя в macOS 14 работал. Результат замера будет ложно оптимистичным. Проверяйте через tcpdump или меряйте с Linux-хоста.
MSS clamping: как чинят на практике
Правильное решение — не трогать MTU на клиентах, а подправлять MSS в проходящих SYN-пакетах на границе сети. Тогда стороны сами договорятся о безопасном размере сегмента, и большие пакеты просто не появятся.
# iptables: подогнать MSS под реальный MTU пути
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --clamp-mss-to-pmtu
# iptables: жёсткое значение, когда PMTU недостоверен
iptables -t mangle -A FORWARD -o ppp0 -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --set-mss 1452
# nftables
nft add rule inet filter forward tcp flags syn \
tcp option maxseg size set rt mtu
# MikroTik RouterOS
/ip firewall mangle add chain=forward action=change-mss \
new-mss=clamp-to-pmtu protocol=tcp tcp-flags=syn \
out-interface-list=VPN passthrough=yes
Два нюанса, на которых обычно спотыкаются. Правило должно ловить и SYN, и SYN-ACK — иначе поправится только одна сторона соединения. И на MikroTik при жёстком значении добавляют tcp-mss=1453-65535, чтобы не поднимать MSS тем, у кого он и так меньше.
Проверить гипотезу за одну команду
Прежде чем перенастраивать сеть, стоит убедиться, что дело действительно в размере пакета. Самый быстрый способ — искусственно занизить MSS и посмотреть, ушла ли проблема.
# Скачать тот же файл с заниженным MSS curl --max-time 30 -o /dev/null https://example.com/big.iso # висит iperf3 -c 203.0.113.10 -M 1300 -t 20 # а так летит # Посмотреть, какой MTU ядро само вывело для маршрута ip route get 8.8.8.8 ip route show cache # Сбросить закешированные значения PMTU ip route flush cache
Если с заниженным MSS всё работает, а с обычным — нет, диагноз подтверждён: где-то на пути пакеты не проходят, а сообщение об этом до вас не доходит.
Почему такую поломку почти невозможно заметить вручную
Проблемы с MTU избирательны: они зависят от маршрута, а значит — от провайдера пользователя и от его географии. Сайт может годами работать для всех, кроме абонентов одного оператора в одном регионе, и никто из команды этого не увидит, потому что сами вы заходите с офисного канала, где всё в порядке.
Именно поэтому проверки из разных точек ловят такие вещи лучше, чем любой разовый тест: одна локация отдаёт таймауты, остальные — норму, и это сразу указывает на маршрут, а не на сервер. Помогает и сравнение результатов ICMP-проверки с HTTP: ping идёт отовсюду, а HTTP-ответ не приходит с одной точки — характерная подпись чёрной дыры MTU.
Пруфен проверяет сайт с интервалом от 2 минут из 13 точек — 11 из них в России — и показывает результат по каждой локации отдельно. Если из двенадцати точек сайт отвечает, а из одной стабильно отваливается по таймауту, это видно на вкладке «По локациям» до того, как придёт первая жалоба от клиента.