Сети для самых маленьких. Часть 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 ───────────►├─────────────────────────────┼─────────────────────────────►│
- Хост шлёт пакет размером в свой MTU, 1500, с флагом DF.
- Роутер, у которого исходящий MTU меньше, выбрасывает пакет и отвечает Fragmentation Needed с MTU следующего хопа в заголовке.
- Стек хоста получает ошибку, находит по вложенному заголовку, к какому адресу назначения она относится, и запоминает: до этого адреса MTU 1492. TCP уменьшает размер сегментов, повторяет потерянное, и дальше всё идёт.
- Запись живёт 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 ходит.
Решения, по убыванию правильности:
- Пропускать ICMP тип 3 код 4 (и тип 11, и тип 3 с кодами 1 и 3). Файрвол, фильтрующий ICMP, должен фильтровать выборочно: закрыть можно echo и timestamp, если очень хочется, но не ошибки.
- MSS clamping на роутере с узким линком — тот приём из четвёртой части. TCP при установлении соединения объявляет максимальный размер сегмента; роутер переписывает его так, чтобы сегмент плюс заголовки помещались в его MTU, и большие пакеты не возникают вовсе. Спасает только TCP.
- Probing без ICMP (RFC 4821):
sysctl net.ipv4.tcp_mtu_probing=1. TCP, заметив, что повторы большого сегмента не доходят, а маленькие доходят, сам пробует промежуточные размеры. Медленнее, но не зависит от доброй воли файрволов. - Понизить 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.
Оставить комментарий могут только зарегистрированные пользователи.
Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.