Сети для самых маленьких. Часть 14: ICMPv6 и Neighbor Discovery

В тринадцатой части мы узнали, что в IPv6 нет широковещания, что на каждом интерфейсе всегда есть link-local адрес и что шлюз по умолчанию хост получает не из настроек, а «из объявления роутера». Всё это держится на одном протоколе — ICMPv6, RFC 4443, который в IPv6 делает работу сразу трёх протоколов из блока про IPv4: ICMP (ошибки и ping), ARP (поиск MAC-адреса соседа) и той части DHCP, которая сообщает хосту о шлюзе. Для последних двух задач внутри ICMPv6 живёт набор сообщений, который называют Neighbor Discovery Protocol, NDP, RFC 4861.

Именно поэтому фраза «запретить ICMP на файрволе», которая в IPv4 ломала только Path MTU Discovery, в IPv6 ломает всё: без ICMPv6 хост не найдёт ни соседа, ни роутер.


ICMPv6: формат и типы

ICMPv6 — это Next Header 58 в заголовке IPv6. Формат тот же, что у ICMP: тип, код, контрольная сумма (здесь обязательная и с псевдозаголовком из адресов, как у UDP и TCP), дальше тело по типу:

 байты:   1       1        2
        ┌──────┬──────┬──────────┬──────────────────────┐
        │ Type │ Code │ Checksum │ тело                 │
        └──────┴──────┴──────────┴──────────────────────┘

Типы поделены по старшему биту: 0–127 — ошибки, 128–255 — информационные. Ошибки, как в IPv4, несут внутри начало исходного пакета, по которому стек находит виноватый сокет.

Type Code Что
1 0 Destination Unreachable: нет маршрута
1 1 запрещено администратором (файрвол)
1 3 адрес недостижим: сосед не ответил на поиск — аналог «Host Unreachable»
1 4 порт недостижим (UDP)
2 0 Packet Too Big — с MTU следующего хопа; единственный механизм PMTUD в IPv6
3 0 Time Exceeded — Hop Limit дошёл до нуля; на нём работает traceroute
3 1 не дождались всех фрагментов
4 0–2 Parameter Problem — ошибка в заголовке, в том числе незнакомый Next Header
128, 129 Echo Request, Echo Reply — ping
130, 131, 132, 143 MLD: запрос, отчёт, выход из группы, отчёт MLDv2
133 Router Solicitation — хост ищет роутер
134 Router Advertisement — роутер объявляет о себе
135 Neighbor Solicitation — хост ищет соседа (бывший ARP-запрос)
136 Neighbor Advertisement — сосед отвечает (бывший ARP-ответ)
137 Redirect

Типы 133–137 — это и есть NDP. У них общее правило: Hop Limit = 255, и получатель отбрасывает любое из них с меньшим значением. Логика простая: пакет с 255 не мог пройти ни одного роутера (тот уменьшил бы его до 254), значит, он пришёл точно из своего сегмента, и подделать его из интернета нельзя.

Параметры NDP-сообщений передаются опциями в формате «тип, длина, значение»: 1 — MAC отправителя, 2 — MAC искомого, 3 — информация о префиксе, 4 — копия перенаправленного пакета, 5 — MTU, 25 — адреса DNS-серверов, 31 — домены поиска. Встретим их ниже.


Поиск соседа: NS и NA

Задача та же, что у ARP: по IPv6-адресу соседа узнать его MAC. Но broadcast-а нет, и спрашивать «всех» нельзя. Вместо этого — трюк с solicited-node multicast.

Для каждого своего unicast-адреса хост подписан на групповой адрес, который получается из младших 24 бит этого адреса:

адрес:            fe80::da47:32ff:fe0a:b0c
младшие 24 бита:                      0a:0b:0c
solicited-node:   ff02::1:ff0a:b0c
MAC группы:       33:33:ff:0a:0b:0c

Хост, которому нужен MAC роутера fe80::da47:32ff:fe0a:b0c, вычисляет эту группу и шлёт запрос в неё. Кадр с MAC-адресом 33:33:ff:0a:0b:0c сетевые карты остальных хостов отбросят аппаратно: они на эту группу не подписаны. Два разных адреса могут совпасть в младших 24 битах и попасть в одну группу, но это редкость, а лишний пакет не страшен: хост сравнит искомый адрес с своими и промолчит.

   хост fe80::3e52:82ff:fe1a:2b3c                          роутер fe80::da47:32ff:fe0a:b0c
   MAC 3c:52:82:1a:2b:3c                                    MAC d8:47:32:0a:0b:0c
        │                                                         │
        ├── NS, на ff02::1:ff0a:b0c (MAC 33:33:ff:0a:0b:0c) ─────►│  (только подписанные)
        │   Target = fe80::da47:32ff:fe0a:b0c                     │
        │   опция 1: мой MAC 3c:52:82:1a:2b:3c                    │
        │                                                         │
        │◄── NA, unicast на fe80::3e52:82ff:fe1a:2b3c ────────────┤
        │   Target = fe80::da47:32ff:fe0a:b0c, флаги R S O        │
        │   опция 2: мой MAC d8:47:32:0a:0b:0c                    │

Neighbor Solicitation (135) несёт в теле искомый адрес (Target) и опцию 1 с MAC отправителя — чтобы отвечающему не пришлось искать его встречным запросом, ровно как SHA в ARP.

Neighbor Advertisement (136) уходит unicast-ом, в теле тот же Target и опция 2 с его MAC. Три флага: S (solicited) — это ответ на запрос, а не объявление по своей инициативе; R (router) — отвечающий является роутером; O (override) — можно перезаписать существующую запись в кеше. Объявление без S, посланное на ff02::1, — аналог gratuitous ARP: так сообщают о смене MAC-адреса.

В tcpdump:

$ sudo tcpdump -i eth0 -n icmp6
IP6 fe80::3e52:82ff:fe1a:2b3c > ff02::1:ff0a:b0c: ICMP6, neighbor solicitation, who has fe80::da47:32ff:fe0a:b0c, length 32
IP6 fe80::da47:32ff:fe0a:b0c > fe80::3e52:82ff:fe1a:2b3c: ICMP6, neighbor advertisement, tgt is fe80::da47:32ff:fe0a:b0c, length 32

Результат попадает в ту же таблицу соседей, что и ARP, с теми же состояниями из седьмой части: INCOMPLETE, REACHABLE, STALE, DELAY, PROBE, FAILED. В IPv6 проверка живости записи названа явно — Neighbor Unreachability Detection: STALE-запись перед использованием подтверждается unicast-NS или ответом верхнего уровня, и только если три NS подряд остались без NA, запись считается мёртвой. В Linux одна команда показывает обе версии:

$ ip -6 neigh
fe80::da47:32ff:fe0a:b0c dev eth0 lladdr d8:47:32:0a:0b:0c router REACHABLE
2001:db8:1234:5678::20 dev eth0 lladdr 00:11:22:33:44:55 STALE

Пометка router — из флага R.


Поиск роутера: RS и RA

В IPv4 хост узнавал шлюз из DHCP или настроек. В IPv6 роутер сам сообщает о себе всем в сегменте, и это самое важное отличие в автонастройке.

Router Advertisement (134) роутер рассылает на ff02::1 (все узлы) периодически, обычно раз в несколько минут, и в ответ на запрос. В нём всё, что хосту нужно знать о сегменте:

$ sudo tcpdump -i eth0 -n -v icmp6 and 'ip6[40] == 134'
IP6 (hlim 255, next-header ICMPv6 (58)) fe80::da47:32ff:fe0a:b0c > ff02::1: [icmp6 sum ok] ICMP6, router advertisement, length 96
	hop limit 64, Flags [none], pref medium, router lifetime 1800s, reachable time 0ms, retrans timer 0ms
	  prefix info option (3), length 32 (4): 2001:db8:1234:5678::/64, Flags [onlink, auto], valid time 86400s, pref. time 14400s
	  mtu option (5), length 8 (1):  1500
	  rdnss option (25), length 24 (3):  lifetime 600s, addr: 2001:db8:1234:5678::1
	  dnssl option (31), length 24 (3):  lifetime 600s, domain(s): example.com.
	  source link-address option (1), length 8 (1): d8:47:32:0a:0b:0c

По строкам:

  • Отправитель — link-local адрес роутера. Именно он становится шлюзом по умолчанию у всех, кто это услышал: default via fe80::da47:32ff:fe0a:b0c proto ra из прошлой части. Глобальный адрес роутера хостам знать не нужно.
  • hop limit 64 — какой Hop Limit ставить в свои пакеты.
  • Flags — два бита, которые решают, нужен ли в сети DHCPv6: M (managed) — «адреса получайте по DHCPv6», O (other) — «адреса делайте сами, но за DNS и прочим идите в DHCPv6». Оба сняты — вся настройка из RA. Подробно в частях 15 и 16.
  • router lifetime 1800s — сколько считать этот роутер шлюзом по умолчанию, если RA перестанут приходить. Ноль означает «я роутер, но не шлюз по умолчанию»: так объявляют префикс, не предлагая себя как выход в интернет.
  • prefix info — префикс сегмента с двумя флагами: onlink (L) — адреса из этого префикса соседи, слать напрямую, без шлюза; auto (A) — из этого префикса можно собрать себе адрес. Это и есть вход в SLAAC. Два срока: valid — сколько адрес из префикса годен вообще, preferred — сколько его можно использовать для новых соединений.
  • mtu — MTU сегмента, если он не стандартный.
  • rdnss, dnssl — адреса DNS-серверов и домены поиска. Появились позже (RFC 8106), чтобы не поднимать DHCPv6 ради одной опции.
  • source link-address — MAC роутера, чтобы сразу положить его в таблицу соседей.

Router Solicitation (133) хост шлёт на ff02::2 (все роутеры) при включении интерфейса, чтобы не ждать следующего периодического RA. Роутер отвечает RA немедленно, unicast-ом или на всех.

Если роутеров несколько, хост ведёт список шлюзов по умолчанию, и в RA есть поле предпочтения (pref medium, high, low). Так в сегменте с основным и резервным роутером переключение на резерв происходит само: основной перестаёт слать RA, через router lifetime его маршрут истекает. Чаще, впрочем, резервный роутер не ждёт этого, а просто берёт на себя тот же link-local адрес.


DAD: проверка, что адрес свободен

Перед тем как использовать любой unicast-адрес — link-local, собранный по SLAAC, выданный DHCPv6 или введённый руками, — хост обязан проверить, что его никто не занял. Это Duplicate Address Detection, и это тот же NS, только особенный:

IP6 :: > ff02::1:ff1a:2b3c: ICMP6, neighbor solicitation, who has 2001:db8:1234:5678:3e52:82ff:fe1a:2b3c, length 24
  • Отправитель — ::, неопределённый адрес: своего у хоста ещё нет, а кандидат использовать нельзя.
  • Получатель — solicited-node группа кандидата: если адрес у кого-то есть, тот на эту группу подписан.
  • Опции с MAC отправителя нет: нечего обновлять в чужих таблицах, адрес не подтверждён.

Хост ждёт секунду. Пришёл NA с этим Target или чужой NS с тем же кандидатом от :: (двое проверяют одно и то же одновременно) — адрес занят, он помечается dadfailed и не используется; стек выберет другой идентификатор, если умеет. Тишина — адрес переходит из tentative в рабочий. В ip -6 addr на только что поднятом интерфейсе первую секунду видно флаг tentative, и именно поэтому IPv6-адрес появляется чуть позже, чем интерфейс.

Если адрес, проверенный DAD, кто-то займёт позже, конфликт проявится как в IPv4: две NA на один NS, скачущий MAC в таблице соседей.


MLD: кто какие группы слушает

Поскольку NDP построен на multicast, кто-то должен знать, какие группы в сегменте кому нужны: коммутатор с MLD snooping не рассылает групповые кадры во все порты, а только туда, где есть подписчики, и роутер пересылает групповой трафик из других сетей только в те сегменты, где его ждут. Для этого хосты отчитываются о подписках протоколом MLD (Multicast Listener Discovery, типы 130–132 и 143): роутер периодически шлёт Query «кто что слушает», хосты отвечают Report со списком групп, а при подписке на новую группу шлют Report сразу. В IPv4 ту же работу делает отдельный протокол IGMP: хосты так же отчитываются роутеру о группах, на которые подписаны; в IPv6 его встроили в ICMPv6 и переименовали.

Знать об MLD нужно по одной причине: коммутатор с MLD snooping, но без MLD-роутера, который задавал бы вопросы, может через некоторое время «забыть» подписки и перестать доставлять NS в solicited-node группы. IPv6 в сегменте перестаёт работать частично и непредсказуемо, а в логах пусто. Если IPv6 ломается через несколько минут после включения коммутатора — проверять MLD snooping.


Redirect

Тип 137, то же, что в ICMP: роутер сообщает хосту, что до адреса X ближе через другой роутер в том же сегменте или что X вообще сосед. В теле — адрес лучшего next hop и копия перенаправленного пакета (опция 4). Правила приёма те же: только от текущего шлюза, только с Hop Limit 255, и во многих системах выключено.


Чем это небезопасно

NDP, как и ARP, никому не верит и никого не проверяет. Атаки те же, что в седьмой части, плюс одна новая и очень частая — rogue RA: любое устройство, пославшее Router Advertisement, становится шлюзом по умолчанию для всего сегмента. Чаще это не злоумышленник, а ошибка: Windows с включённым общим доступом к интернету, виртуальная машина с настроенным radvd, домашний роутер, принесённый в офис. Все хосты получают второй шлюз, половина трафика уходит в никуда, и, в отличие от DHCP, где клиент берёт один ответ, здесь хост честно пользуется обоими роутерами по очереди.

Защита — на коммутаторе: RA Guard (RFC 6105) пропускает RA только с доверенных портов, как DHCP snooping из одиннадцатой части. Существует и криптографическая версия NDP, SEND (Secure Neighbor Discovery): адрес выводится из открытого ключа хоста, а сообщения подписываются, так что подделать NA или RA от чужого имени нельзя. Поддержки в массовых системах почти нет, и в жизни SEND не встречается.


Что нельзя фильтровать

Памятка из восьмой части, переписанная для IPv6 (по RFC 4890):

Обязаны проходить везде, иначе IPv6 не работает:

  • 1 Destination Unreachable, 2 Packet Too Big, 3 Time Exceeded, 4 Parameter Problem — ошибки;
  • 133–136 — RS, RA, NS, NA — в пределах сегмента (они и так не выйдут за него из-за Hop Limit 255 и link-local адресов, но файрвол на самом хосте может их зарезать);
  • 130–132, 143 — MLD, если в сети есть коммутаторы со snooping.

Можно ограничить: 128 Echo Request снаружи (как и в IPv4, лишая себя диагностики), 137 Redirect.

Правило «deny icmp any any» в IPv6 на хосте приводит к тому, что хост перестаёт отвечать на NS и через полминуты исчезает для соседей: записи о нём устаревают, NUD не получает подтверждения, и даже роутер больше не может доставить ему пакет. Это самая частая причина «IPv6 работал и перестал» после включения файрвола.


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

Таблица соседей и её состояния:

$ ip -6 neigh show nud reachable
$ ip -6 neigh show nud failed

Спросить роутеры вручную и увидеть RA с разбором (пакет ndisc6):

$ sudo rdisc6 eth0
Soliciting ff02::2 (ff02::2) on eth0...

Hop limit                 :           64 (      0x40)
Stateful address conf.    :           No
Stateful other conf.      :           No
Router preference         :       medium
Router lifetime           :         1800 (0x00000708) seconds
 Prefix                   : 2001:db8:1234:5678::/64
  On-link                 :          Yes
  Autonomous address conf.:          Yes
  Valid time              :        86400 (0x00015180) seconds
  Pref. time              :        14400 (0x00003840) seconds
 MTU                      :         1500 bytes (valid)
 Recursive DNS server     : 2001:db8:1234:5678::1
 Source link-layer address: D8:47:32:0A:0B:0C
 from fe80::da47:32ff:fe0a:b0c

Если rdisc6 молчит — в сегменте нет роутера IPv6 или его RA не доходят. Два ответа от разных адресов — rogue RA, и второй стоит найти по MAC.

Поиск соседа вручную — ndisc6 fe80::da47:32ff:fe0a:b0c eth0, аналог arping. Отключить приём RA на машине, которая сама роутер: sysctl net.ipv6.conf.eth0.accept_ra=0. И полный захват NDP — tcpdump -i eth0 -n -v icmp6: за первые секунды после ip link set eth0 up там будут DAD для link-local, RS, RA, DAD для глобального адреса и первые NS/NA — весь протокол в десяти строках.


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

  • ICMPv6 (Next Header 58) объединяет ошибки, ping, поиск соседей и роутеров, MLD. Ошибки — типы до 127, остальное — от 128.
  • Поиск соседа: NS в solicited-node группу ff02::1:ffXX:XXXX по младшим 24 битам адреса, кадр на 33:33:ff:XX:XX:XX, чужие карты отбрасывают его сами; NA unicast-ом с флагами S, R, O. Таблица соседей и NUD — как у ARP.
  • Роутер объявляет себя RA на ff02::1: link-local отправителя становится шлюзом, флаги M и O решают про DHCPv6, prefix info с флагом A — вход в SLAAC, RDNSS — DNS. RS ускоряет первый RA.
  • DAD: NS от :: на свой будущий адрес; тишина секунду — адрес рабочий.
  • Все NDP-сообщения с Hop Limit 255, иначе отбрасываются. Защиты от подделки нет: rogue RA — частая авария, RA Guard — лечение.
  • Фильтровать ICMPv6 целиком нельзя: типы 1–4 и 133–136 обязаны проходить.

Дальше — Часть 15: SLAAC: как из префикса в RA и идентификатора интерфейса хост собирает себе адрес без единого сервера, откуда берётся идентификатор (EUI-64, стабильный случайный, временный), что значат сроки valid и preferred, как адреса устаревают и заменяются, и как это настроить на роутере с Linux.

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

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