Сети для самых маленьких. Часть 8: ICMP

В шестой части роутер несколько раз выбрасывал пакет и сообщал об этом отправителю: когда TTL дошёл до нуля, когда не нашлось маршрута, когда пакет с флагом DF не влез в MTU. IP сам по себе ничего сообщить не может: в его заголовке нет места для ошибок, а роутер не хранит состояния. Для этого рядом с IP с самого начала живёт второй протокол — ICMP, Internet Control Message Protocol (RFC 792). Он же под капотом у двух самых известных сетевых команд, ping и traceroute, и он же — причина половины загадочных проблем вида «пинг ходит, а сайт не открывается».


Что такое ICMP

ICMP — протокол сетевого уровня, но ездит внутри IP-пакета с полем Protocol = 1, как будто он транспортный. Это не противоречие, а удобство: так его может обработать любой стек, умеющий IP. Заголовок — 8 байт:

 байты:   1       1        2              4
        ┌──────┬──────┬──────────┬──────────────────────┐
        │ Type │ Code │ Checksum │ зависит от типа      │  затем данные
        └──────┴──────┴──────────┴──────────────────────┘

Type — вид сообщения, Code — уточнение внутри вида. Сообщения делятся на две группы, и это деление важнее номеров.

Запросы и ответы — ICMP как самостоятельный протокол. Главная пара:

Type Что
8 Echo Request — «ответь мне»
0 Echo Reply — ответ

Четыре байта после заголовка здесь — Identifier и Sequence Number, а дальше произвольные данные, которые ответ копирует без изменений.

Сообщения об ошибках — то, ради чего ICMP существует. Отправляются в ответ на чужой пакет, который не удалось доставить:

Type Code Что случилось Кто шлёт
3 0 Network Unreachable — нет маршрута к сети роутер
3 1 Host Unreachable — сеть есть, хост не отвечает (ARP) последний роутер
3 2 Protocol Unreachable — у получателя нет такого протокола хост
3 3 Port Unreachable — никто не слушает порт (UDP) хост
3 4 Fragmentation Needed — пакет с DF не влезает в MTU роутер
3 13 Communication Administratively Prohibited — запрещено файрволом роутер или хост
5 0, 1 Redirect — для этой сети/хоста есть шлюз получше роутер
11 0 Time Exceeded in Transit — TTL дошёл до нуля роутер
11 1 Fragment Reassembly Time Exceeded — не все фрагменты дошли хост
12 0 Parameter Problem — ошибка в заголовке IP любой

Ключевая деталь: в данных каждого сообщения об ошибке лежит заголовок IP исходного пакета и первые 8 байт его данных. Восьми байт хватает, чтобы захватить порты TCP или UDP, а значит, стек, получивший ошибку, может понять, какого сокета она касается, и отдать её нужному приложению. Именно поэтому connect() возвращает «No route to host» или «Connection refused» сразу, а не по таймауту. Современные стеки кладут в ошибку больше, до 576 байт пакета, но правило про 8 байт — минимум.

У типа 3 с кодом 4 есть ещё одно поле: во вторых двух байтах «зависит от типа» роутер пишет MTU следующего хопа. На нём держится Path MTU Discovery, раздел ниже. У Redirect там адрес шлюза, который надо использовать.

Правила, чтобы ICMP не породил лавину:

  • ошибки не отправляются в ответ на ICMP-ошибки (но отправляются в ответ на echo: иначе ping не работал бы);
  • ошибки не отправляются в ответ на пакеты на широковещательные и групповые адреса и на фрагменты, кроме первого;
  • отправка ошибок ограничена по частоте: Linux по умолчанию шлёт не чаще одной в секунду на адрес назначения для большинства типов.

Последнее правило объясняет пропуски в выводе traceroute: роутер жив, но отвечать всем подряд не обязан.


ping

ping отправляет Echo Request и ждёт Echo Reply, измеряя время между ними:

$ ping -c 3 203.0.113.10
PING 203.0.113.10 (203.0.113.10) 56(84) bytes of data.
64 bytes from 203.0.113.10: icmp_seq=1 ttl=55 time=12.3 ms
64 bytes from 203.0.113.10: icmp_seq=2 ttl=55 time=12.1 ms
64 bytes from 203.0.113.10: icmp_seq=3 ttl=55 time=12.4 ms

--- 203.0.113.10 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 12.112/12.267/12.401/0.120 ms

Что здесь что:

  • 56(84) bytes — 56 байт данных, с 8 байтами ICMP и 20 байтами IP — 84. В данных ping кладёт отметку времени отправки, по которой считает time из вернувшейся копии, и заполнитель.
  • icmp_seq — Sequence Number, растёт на единицу. Пропуски номеров — потери, повторы — дубликаты (бывает в петлях и при двух путях).
  • ttl=55 — TTL в ответе, то есть то, что осталось от начального значения сервера после обратного пути. Сервер с TTL 64 в девяти хопах от нас. Это значение про обратный путь, не про прямой.
  • time — RTT, туда и обратно. Половина — не задержка в одну сторону: пути могут быть несимметричны.

Два разных «не отвечает», которые ping показывает по-разному и которые нужно различать:

Вывод Значит
From 192.168.1.10 icmp_seq=1 Destination Host Unreachable свой же стек: сосед или шлюз не ответил на ARP (часть 7). Проблема в своём сегменте
From 198.51.100.1 icmp_seq=1 Destination Net Unreachable роутер 198.51.100.1 не нашёл маршрута. Пакет дошёл до него и упёрся
From 198.51.100.1 ... Packet filtered тип 3 код 13: файрвол на этом роутере запретил
From 203.0.113.9 ... Time to live exceeded петля маршрутизации, пакет крутился, пока не кончился TTL
тишина, потом 100% packet loss пакеты или ответы теряются молча: хост выключен, файрвол без ответа, ICMP закрыт

Ключи, которые пригодятся дальше: -s 1472 — размер данных, -M do — ставить DF и не разрешать фрагментацию, -t 5 — начальный TTL, -I eth1 — интерфейс. На Windows ping -l 1472 -f — то же про размер и DF, и там четыре запроса вместо бесконечности по умолчанию.

Отвечать на echo обязаны все стеки, но администраторы нередко закрывают его «от сканирования». Поэтому молчание на ping ничего не доказывает: хост может быть жив и закрыт. А вот ответ доказывает всё: хост жив, маршрут в обе стороны есть.


traceroute

Как узнать все роутеры на пути, если IP их не записывает? Через TTL. Отправляем пакет с TTL = 1: первый роутер уменьшает его до нуля, выбрасывает и присылает Time Exceeded со своего адреса. Теперь мы знаем первый хоп. TTL = 2 — второй роутер. И так, пока пакет не дойдёт до цели:

$ traceroute 203.0.113.10
traceroute to 203.0.113.10 (203.0.113.10), 30 hops max, 60 byte packets
 1  192.168.1.1 (192.168.1.1)  0.412 ms  0.388 ms  0.371 ms
 2  198.51.100.1 (198.51.100.1)  1.924 ms  1.901 ms  1.887 ms
 3  198.51.100.65 (198.51.100.65)  4.102 ms  4.088 ms  4.071 ms
 4  * * *
 5  203.0.113.1 (203.0.113.1)  11.512 ms  11.498 ms  11.480 ms
 6  203.0.113.10 (203.0.113.10)  12.301 ms  12.288 ms  12.270 ms

На каждый TTL уходит три пробы, отсюда три времени. Как traceroute понимает, что дошёл? Для этого пробы делают такими, чтобы и конечный хост на них ответил ошибкой:

  • классический Unix-traceroute шлёт UDP на заведомо несуществующие порты, начиная с 33434. Роутеры отвечают Time Exceeded, а конечный хост — Port Unreachable (тип 3 код 3), и это сигнал остановиться;
  • tracert в Windows и traceroute -I шлют ICMP Echo, и концом служит Echo Reply;
  • traceroute -T шлёт TCP SYN на порт 80 или 443: его пропустят файрволы, которые режут и UDP на странные порты, и ICMP.

Строка * * * — хоп, с которого не пришло ни одного ответа за таймаут. Причины: роутер не шлёт Time Exceeded (так настроен), шлёт, но не чаще раза в секунду и мы не попали, или его ответы фильтруются по дороге. Если после звёздочек путь продолжается, хоп просто молчаливый, и это не проблема. Если звёздочки до конца — пакеты дальше не идут, и проблема на последнем ответившем роутере или сразу за ним.

Что traceroute не показывает:

  • Обратный путь. Каждый ответ пришёл своей дорогой, и она может отличаться от прямой. Время на хопе 4 больше, чем на хопе 5, — обычное дело: роутер 4 просто медленно генерирует ICMP или его ответ шёл длиннее.
  • Один путь. При балансировке трафика между равноценными маршрутами роутер раскидывает пакеты по хешу от адресов и портов. У классического traceroute порт назначения меняется с каждой пробой, и три пробы одного хопа могут уйти тремя разными путями. Вывод тогда «прыгает». Инструмент paris-traceroute и ключ -U держат порты постоянными.
  • Адрес, которым роутер смотрит вперёд. Time Exceeded уходит с адреса того интерфейса, на который пришёл пакет, то есть с «входного» адреса каждого роутера.

Инструмент mtr объединяет ping и traceroute: гоняет пробы постоянно и показывает потери и задержки по каждому хопу накопительно. Для диагностики «где теряется» он удобнее обоих.


Path MTU Discovery

Вернёмся к фрагментации из шестой части. Современный стек ставит DF на все пакеты и полагается на то, что сеть сообщит ему, если пакет не влезает. Это и есть Path MTU Discovery (RFC 1191):

 хост                        R1 (MTU 1500)               R2 (MTU 1492, PPPoE)             сервер
  │                             │                             │                              │
  ├── 1500 байт, DF ───────────►├── 1500 байт, DF ───────────►│ не влезает, DF: выбросить    │
  │                             │                             │                              │
  │◄── ICMP 3/4, MTU = 1492 ────┼─────────────────────────────┤                              │
  │                             │                             │                              │
  │ запомнить: до сервера 1492  │                             │                              │
  ├── 1492 байт, DF ───────────►├─────────────────────────────┼─────────────────────────────►│
  1. Хост шлёт пакет размером в свой MTU, 1500, с флагом DF.
  2. Роутер, у которого исходящий MTU меньше, выбрасывает пакет и отвечает Fragmentation Needed с MTU следующего хопа в заголовке.
  3. Стек хоста получает ошибку, находит по вложенному заголовку, к какому адресу назначения она относится, и запоминает: до этого адреса MTU 1492. TCP уменьшает размер сегментов, повторяет потерянное, и дальше всё идёт.
  4. Запись живёт 10 минут, после чего стек пробует снова увеличить размер: путь мог измениться.

Посмотреть, что стек запомнил, можно так:

$ ip route get 203.0.113.10
203.0.113.10 via 192.168.1.1 dev eth0 src 192.168.1.10 uid 1000
    cache expires 598sec mtu 1492

Чёрная дыра

Схема работает ровно до тех пор, пока ICMP-ошибка доходит до отправителя. А между роутером R2 и хостом может стоять файрвол, настроенный «ICMP — это ping, ping нам не нужен, запретить ICMP целиком». Тогда:

  • маленькие пакеты (запросы, страницы входа, ping) проходят;
  • большой пакет (ответ с картинкой, файл, вывод команды) выбрасывается на R2, а сообщение об этом до сервера не доходит;
  • сервер повторяет тот же пакет того же размера, снова и снова, пока TCP не сдастся.

Со стороны это выглядит как «сайт начинает открываться и виснет», «SSH пускает, а ls в большом каталоге повисает», «VPN поднимается, но ничего не работает». Это PMTU black hole, и это, пожалуй, самая частая сетевая проблема, которую не могут найти неделями, потому что ping ходит.

Решения, по убыванию правильности:

  1. Пропускать ICMP тип 3 код 4 (и тип 11, и тип 3 с кодами 1 и 3). Файрвол, фильтрующий ICMP, должен фильтровать выборочно: закрыть можно echo и timestamp, если очень хочется, но не ошибки.
  2. MSS clamping на роутере с узким линком — тот приём из четвёртой части. TCP при установлении соединения объявляет максимальный размер сегмента; роутер переписывает его так, чтобы сегмент плюс заголовки помещались в его MTU, и большие пакеты не возникают вовсе. Спасает только TCP.
  3. Probing без ICMP (RFC 4821): sysctl net.ipv4.tcp_mtu_probing=1. TCP, заметив, что повторы большого сегмента не доходят, а маленькие доходят, сам пробует промежуточные размеры. Медленнее, но не зависит от доброй воли файрволов.
  4. Понизить MTU интерфейса на хосте. Грубо, но работает.

Найти чёрную дыру можно тем же ping с DF и подбором размера. 1472 байта данных плюс 28 байт заголовков — ровно 1500:

$ ping -M do -s 1472 203.0.113.10
ping: local error: message too long, mtu=1492

Это местный стек уже знает про 1492 из прошлой ошибки. Если бы не знал, а дыра была, ответов просто не пришло бы, а -s 1464 прошёл бы. Так, двоичным поиском, находят реальный MTU пути.


Redirect

В сегменте два роутера: R1 — шлюз по умолчанию, R2 ведёт в сеть 10.0.0.0/8. Хост знает только R1. Пакет в 10.1.2.3 уходит R1, тот смотрит в таблицу и видит, что next hop — R2, в том же сегменте, откуда пакет пришёл. R1 пересылает пакет R2 и посылает хосту ICMP Redirect: «для 10.1.2.3 пользуйся R2». Хост добавляет временный маршрут и дальше ходит в 10.0.0.0/8 напрямую.

Удобно: не нужно настраивать маршруты на каждом хосте. Опасно: любой в сегменте может прислать redirect и увести трафик хоста на себя, ровно как с ARP в прошлой части. Поэтому стеки принимают redirect только от своего текущего шлюза и только на адрес в своей сети, а во многих системах его выключают совсем (net.ipv4.conf.all.accept_redirects=0), а роутерам запрещают отправлять. Если в ip route get появился маршрут с пометкой redirected, вы знаете, откуда он.


Что можно фильтровать

Поскольку ICMP — любимая жертва файрволов, короткая памятка.

Должно проходить всегда, иначе ломается сеть:

  • тип 3 код 4 — Fragmentation Needed (иначе чёрные дыры);
  • тип 11 — Time Exceeded (иначе не работает traceroute и теряется диагностика петель);
  • тип 3 коды 0, 1, 3, 13 — без них приложения ждут таймаут вместо мгновенной ошибки.

Можно закрыть, если есть причина: echo request снаружи (при этом вы лишаетесь ping как инструмента диагностики для себя же), timestamp (тип 13/14, выдаёт время на хосте), redirect на границе сети.

Старая атака, из-за которой включают лишнее: smurf — echo request на широковещательный адрес чужой сети с подменённым адресом отправителя, и все хосты сети отвечают жертве. Защита — не отвечать на echo по широковещательным адресам, у Linux это net.ipv4.icmp_echo_ignore_broadcasts=1 по умолчанию, и запрещать направленное широковещание на роутерах. Закрывать ICMP целиком для этого не нужно.

В IPv6 всё это умножается: там ICMPv6 отвечает ещё и за то, что в IPv4 делает ARP, и фильтрация «всего ICMP» ломает сеть немедленно, а не через полчаса. Об этом в части 14.


Смотрим руками

Весь ICMP на интерфейсе, с типами и кодами:

$ sudo tcpdump -i eth0 -n -v icmp
IP (tos 0x0, ttl 64, id 1, offset 0, flags [DF], proto ICMP (1), length 84)
    192.168.1.10 > 203.0.113.10: ICMP echo request, id 1234, seq 1, length 64
IP (tos 0x0, ttl 55, id 0, offset 0, flags [none], proto ICMP (1), length 84)
    203.0.113.10 > 192.168.1.10: ICMP echo reply, id 1234, seq 1, length 64
IP (tos 0xc0, ttl 63, id 0, offset 0, flags [none], proto ICMP (1), length 576)
    198.51.100.1 > 192.168.1.10: ICMP 203.0.113.10 unreachable - need to frag (mtu 1492), length 556
        IP (tos 0x0, ttl 63, id 2, offset 0, flags [DF], proto TCP (6), length 1500)
    192.168.1.10.51234 > 203.0.113.10.443: ...

Третье сообщение — Fragmentation Needed с MTU и вложенным заголовком исходного пакета, который tcpdump тоже разобрал: видно, что это был TCP-сегмент на 443 порт длиной 1500. Так и ловят чёрные дыры: если такие сообщения видны на роутере, но не на хосте, их кто-то режет по дороге.

Сравнить ping с ICMP, UDP и TCP, когда что-то фильтруется: traceroute -I, traceroute (UDP), traceroute -T -p 443. Если доходит только последний — на пути файрвол, пропускающий лишь нужные порты.

И mtr -n 203.0.113.10 на минуту, чтобы увидеть, на каком хопе начинаются потери. Потери на промежуточном хопе, которых нет на последнем, — не потери, а роутер, ограничивающий ICMP; потери, которые с какого-то хопа идут до конца, — настоящие.


Что запомнить

  • ICMP — сообщения об ошибках и диагностика рядом с IP. В каждой ошибке — заголовок исходного пакета и 8 байт данных, по которым стек находит виноватый сокет.
  • ping — echo request/reply; Destination Host Unreachable от своего адреса — ARP, от чужого — маршрутизация. TTL в ответе говорит об обратном пути.
  • traceroute поднимает TTL с единицы и собирает Time Exceeded с каждого роутера; конец — Port Unreachable или Echo Reply. Показывает прямой путь, по одному из возможных.
  • Path MTU Discovery: DF плюс Fragmentation Needed с MTU в ответе. Файрвол, режущий ICMP, создаёт чёрную дыру: маленькое проходит, большое виснет. Лечение — пропускать тип 3 код 4, MSS clamping, tcp_mtu_probing.
  • Redirect — подсказка «есть шлюз ближе», полезная и опасная; обычно выключен.

На этом блок про IPv4 закончен: адреса, маршрутизация, перевод в MAC и сообщения об ошибках. Дальше — транспортный уровень, и начнём с простого: UDP. Восемь байт заголовка, порты и сокеты, что такое «без соединения» и кому это подходит, и как UDP ведёт себя за NAT.

Оставить комментарий могут только зарегистрированные пользователи.

Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.