В нашей модели компьютерных сетей пакеты не перелетают с узла на узел волшебным образом, а путешествуют по каналам. Пока мы работаем на уровне IP, непосредственная передача пакета напрямую, "из рук в руки", возможна только узлу, который подключен к тому же каналу, что и наш локальный узел.
Нам удобнее думать о сетевом взаимодействии узлов, поставив себя на место одного из них и назвав его локальным узлом. Тогда все прочие узлы — удаленные.
Сам процесс передачи пакета по каналу может быть сколь угодно сложным и даже рекурсивно вызывать уровень IP, как это происходит в туннелях. Однако благодаря сознательному и целенаправленному разделению уровней в сетевой архитектуре это происходит прозрачно и выглядит для уровня-потребителя как один элементарный акт.
Чтобы нам вскоре не пришлось опровергать наши собственные утверждения, мы сразу же должны оговорить, что подключение узлов к общему каналу — это необходимое, но еще недостаточное условие для прямой передачи пакета между ними. Дело в том, что существуют "неклассические" каналы, в которых не все пары узлов могут обмениваться пакетами напрямую. Подробному обсуждению таких каналов в контексте IPv6 мы посвятим §5.2, а пока что давайте предположим, что наш локальный узел А априори знает, что он может передать пакет узлу Б напрямую, используя определенный канал К. Если это так, то узлы А и Б — соседи (neighbors) по каналу К. Пока наше воображение еще не смущено "неклассическими" каналами, мы можем позволить себе упрощенную картину, где соседи А и Б соединены каналом "точка-точка" — например, старым добрым PPP — или же широковещательным каналом — скажем, привычной локальной сетью Ethernet. Эти хрестоматийные случаи иллюстрирует рис 5.1(рис 5.1) Узлы-соседи по широковещательному каналу (слева) и по каналу "точка-точка" (справа)
Тем не менее, давайте с самого начала иметь в виду, что, с математической точки зрения, отношение соседства коммутативно, но не транзитивно: если узел Б — сосед узла А, то узел А — сосед узла Б; но если узлы А и Б соседи и узлы Б и В соседи, то это еще не значит, что узлы А и В тоже соседи.
Коммутативность соседства предполагает, что канал двунаправленный. Само понятие соседства нам нужно для разработки механизмов, ориентированных на двунаправленные каналы, так что однонаправленные каналы мы рассматривать не станем.
Еще одно свойство соседства, кажущееся самоочевидным, — это его рефлексивность: всякий узел является собственным соседом и не нуждается в маршрутизаторе, чтобы разговаривать сам с собой. Но в действительности оно не возникает из воздуха, а реализовано с помощью сетевого интерфейса типа "петля" и связанного с ним виртуального канала из одного узла, на котором и возникает этот вырожденный случай соседства. Когда эта схема дает сбой, возникают артефакты, такие как разговор узла PPP с самим собой через удаленного соседа.
Теперь подумаем, как локальный узел А обращается к соседу Б. Использует ли он для этого титул, кличку или радиопозывные? Конечно же, нет. Вместо этого узел А применяет адрес IP, присвоенный интерфейсу узла Б. Если у узла Б несколько интерфейсов, то мы имеем в виду, конечно же, один из подключенных к общему каналу К.
Таким образом, узел IP, намереваясь передать индивидуальный пакет, вначале знает только адрес IP удаленного соседа. Так как сосед может быть не только адресатом пакета, но и маршрутизатором, его называют "следующий шаг" (next hop), тем самым подчеркивая, что он может быть не последним на пути пакета.
Следующий шаг пакета от создавшего его хоста иногда называют "первый шаг" (first hop). На самом деле, эта деталь редко имеет значение, так что первый шаг пакета — всего лишь частный случай следующего шага.
Однако, чтобы на самом деле передать пакет дальше, уровень IP должен обратиться к канальному уровню, а тот использует только канальную адресацию и ожидает канального адреса соседа, будь то в явном или неявном виде. Поэтому локальный узел должен найти среди всех соседей по данному каналу именно того, кто обладает адресом следующего шага, и узнать его канальный адрес. Эта процедура известна нам как разрешение адресов (address resolution).
Если канал — типа "точка-точка", то это тривиальная задача, поскольку явный адрес IP и неявный канальный адрес единственного соседа заранее известны.
Напомним себе и читателям, что в определенных условиях бывает достаточно неявной адресации. Так, в канале "точка-точка" явная канальная адресация излишня, потому что любая передача в этот канал будет принята ровно одним узлом, которому она и предназначалась. Можно сказать, что адрес соседа по каналу "точка-точка" звучит как "вон тот узел на другом конце провода". Поэтому, например, присваивать адреса MAC интерфейсам на концах канала "точка-точка" было бы избыточным, хотя на это нет принципиального запрета.
Если же канал многоадресный, то задача существенно затрудняется; ее решение зависит от свойств этого канала. В худшем случае, когда нет
никаких вспомогательных механизмов, поможет только заданная вручную таблица соответствия между адресами сетевого уровня и канальными
адресами. Но, к счастью, такой случай практически не встречается, а зато мы довольно часто сталкиваемся с каналами, где есть возможность
обратиться сразу ко всем соседям и спросить, кому принадлежит искомый сетевой адрес. Эта идея, представленная на рис. 5.2, открывает нам
путь к автоматизированному розыску
Этим свойством, когда узел может одновременно обратиться ко всем соседям, обладают не только широковещательные каналы, такие как
Ethernet. Скажем, канал "точка-точка" тоже поддерживает широковещательную и групповую адресацию, поскольку сосед заведомо один.
Что же касается широковещательного канала, то он дает более сильные гарантии: все подключенные к нему узлы, независимо от их числа, —
попарно соседи, и вдобавок между ними возможно широковещание.
Хотя на первый взгляд может показаться, что разрешение адресов и розыск соседей — одна и та же задача, на самом деле они перекрываются, но не совпадают. Розыск соседей, в общем случае, можно вести по любым критериям, а не только по сетевому адресу. Например, узел может спросить у своих соседей: "Эй, кто из вас — маршрутизатор по умолчанию?" — или: "У кого это из вас жужжит вентилятор?" С другой стороны, разрешение адресов не всегда требует розыска соседей. Так, мы обсудили в §4.1.1, что разрешение групповых адресов IPv4 и IPv6 в групповые же адреса MAC происходит локально, простой подстановкой битов. Розыск не потребуется и тогда, когда требуемые сведения заранее внесены в локальную конфигурацию — то, что мы знали в IPv4 как статический ARP (static ARP).
История вычислительных сетей знает и другие примеры локального разрешения адресов. Так, в адресе IPX идентификатор интерфейса всегда совпадал с его канальным адресом MAC, так что найти канальный адрес соседа можно было, опустив те старшие биты адреса IPX, где находился префикс подсети.
Это логическое разделение полезно иметь в виду, чтобы наше решение не вышло узким и ограниченным. Тем не менее, первой прикладной задачей у нас стоит именно разрешение индивидуальных адресов IPv6 в канальные адреса, когда узел А спрашивает: "Дорогие соседи, кому из вас принадлежит сетевой адрес Б?" — и получает в ответ канальный адрес искомого соседа, если тот на месте.
Нетерпеливые читатели могут найти полный список функций, которые выполняет розыск соседей IPv6, в [§3 RFC 4861]. Но не стоит торопиться, ибо мы придем к этим функциям естественным путем.
Для начала мы рассмотрим уже готовое решение из нашего арсенала: ARP. Этот протокол достаточно гибок, чтобы работать с разными типами канальных и сетевых адресов, так что мы могли бы легко приспособить его и для нужд IPv6. Тем не менее, у нас есть повод, чтобы отказаться от ARP и создать новый механизм розыска соседей IPv6. В вину ARP мы поставим его… широковещание. Как так? Ведь мы сами только что сказали, что широковещание открывает нам светлый путь к розыску соседей! Не подлежит сомнению, что это была плодотворная идея. Но, чтобы перейти "от идеи к модели", нам надо учесть современные возможности каналов. В самом начале они поддерживали только два режима передачи, широковещательный и индивидуальный; однако с тех пор появился и прочно утвердился еще один режим, групповой.
В чем широковещательный режим проигрывает групповому? Прежде всего, у широковещания нулевая избирательность. Представим себе канал в смешанной сети, где одни узлы поддерживают только IPv4, другие — только IPv6, третьи — только ISO и т.д. Всякий раз, когда узел IPv4 посылает в такой канал запрос ARP, его принимают, анализируют и только затем отбрасывают все те узлы, которым вообще нет дела до IPv4; их вычислительные ресурсы расходуются при этом совершенно впустую, а в большой сети непроизводительный расход "виртуальной энергии" достигает заметной величины.
Ходит легенда о больших плоских ЛВС, настолько перегруженных широковещательным трафиком, что только самый современный и мощный компьютер может справиться с его приемом. Если же компьютер слабоват, то он практически повиснет, пытаясь принять, чтобы тут же отбросить, бесконечный поток не интересных ему широковещательных кадров.
Кстати, потребление электроэнергии компонентами и узлами сети тоже зависит от того, насколько оптимизировано в ней распространение трафика.
Это положение можно было бы значительно улучшить, если бы розыск соседей перешел на групповое вещание. В простом канале типа "общая шина" нежелательный групповой трафик отфильтруют сетевые адаптеры, а в современной коммутируемой ЛВС он даже не попадет в те сегменты, где его никто не ожидает. Вот нам задача номер один.
Например, коммутируемая ЛВС Ethernet сможет оптимизировать групповой трафик, если в ней применяется "подслушивание IGMP/MLD" (IGMP/MLD snooping) [RFC 4541] (см. §6.4) или специализированный протокол GMRP [IEEE 802.1ak 2007].
В IPv4 существовала группа "все узлы данной подсети", 224.0.0.1, а ей отвечал определенный групповой канальный адрес. Например, в Ethernet это был адрес MAC 01-00-5E-00-00-01. Но, увы, построить на этом стандартный механизм розыска соседей было нельзя, потому что поддержка группового вещания в IPv4 так и осталась необязательной [§3.3.7 RFC 1122].
Теперь же, в IPv6, групповое вещание — обязательная возможность (см. §2.8), и мы могли бы основать розыск соседей на групповом адресе "все узлы канала", FF02::1. Это сразу ограничило бы круг вовлеченных в него узлов только теми, что поддерживают IPv6. Но не можем ли мы придумать решение еще лучше?
Подсказкой нам послужит внушительная длина адреса IPv6. А что если мы закодируем в групповом адресе самую характерную часть искомого индивидуального адреса? Положим, канал объединяет какое-то число узлов IPv6. Их интерфейсам, смотрящим в этот канал, назначена одна и та же подсеть, но идентификатор интерфейса у каждого свой. Кроме того, мы заранее знаем, что длина этого идентификатора 64 бита. Значит, мы могли бы перенести их во внутриканальный групповой адрес с определенным префиксом, назначенным для этой цели. Допустим, если бы это был префикс FF02::/64, то из индивидуального адреса 2001:DB8:1:2:3:4:5:6 (И) мы получили бы групповой адрес FF02::3:4:5:6 (Г) . Теперь узел Б, владелец адреса И, должен вступить в группу Г и принимать запросы о розыске соседей. Его сосед А, разыскивающий адрес И, пошлет запрос по адресу Г и прицельно попадет запросом в узел Б, потому что в данной подсети ни у кого больше нет такого идентификатора интерфейса.
На словах звучит эффектно; а что произойдет в действительности? Прежде всего, узел А должен преобразовать адрес Г в групповой канальный адрес. Эта процедура не представляет сложности, потому что она выполняется локально (см. §4.1.1), однако часть битов идентификатора группы при этом, как правило, теряется. Так, например, при инкапсуляции Ethernet из группового адреса IPv6 в групповой адрес MAC переходят только младшие 32 бита. Кроме того, каждая группа занимает какое-то количество ресурсов в интеллектуальной ЛВС, оптимизирующей групповой трафик. Говоря проще, коммутаторам такой ЛВС приходится следить за "географией" каждой группы. Поэтому давайте ограничим общее число групп, которые могут возникнуть на одном канале при розыске соседей. Для этого пусть в групповой адрес Г попадают только избранные биты адреса И.
Но сколько и каких битов искомого адреса будет лучше всего взять? Мы уже выяснили, что они должны принадлежать идентификатору интерфейса. Когда администратор сети назначает эти идентификаторы вручную, то, скорее всего, он будет выбирать небольшие числа: 1, 2, 3… Если же идентификатор создается из адреса MAC 48 по рецепту §2.7, то мы должны учесть структуру этого адреса. Старшие 24 бита в нем — это OUI. Если, например, организация закупает для своей сети оборудование избранного производителя, то поле OUI в его адресах MAC окажется одинаковым (или будет принимать ограниченный набор значений). В то же время, младшие 24 бита в адресе MAC (добавочный идентификатор) носят куда более случайный характер, потому что в пределах одного OUI они уникальны, а каждый производитель назначает их независимо от своих конкурентов. Наконец, если у интерфейса есть адрес EUI-64, то добавочный идентификатор занимает в нем 40 бит. Их распределение зависит от политики производителя; в частности, при последовательном назначении старшие биты могут совпадать в пределах одной партии оборудования, а младшие будут разными. Таким образом, младшие 24 бита искомого адреса — это хороший выбор при разных политиках назначения идентификаторов интерфейсам. Тогда общее число групп, которые могут возникнуть в одном канале при розыске соседей, не превысит $$2^24$$. Да, это тоже немало, но, тем не менее, существенно меньше, чем первоначальные $$2^63$$.
Попытайтесь вспомнить или выяснить, почему $$2^63$$, а не $$2^64$$. (Подсказка: в формате "модифицированный EUI-64" тоже есть бит I/G, он же g, а розыск ведется только по индивидуальному адресу.)
Итак, мы вполне обоснованно решили, что в особый групповой адрес Г попадают только 24 младших бита искомого адреса И. Соответственно, заранее известный групповой префикс должен быть длиной 104 бита. На эту роль назначен общепринятый префикс FF02::1:FF00:0/104 [§2.7.1 RFC 4291]. Например, из нашего адреса И, то есть 2001:DB8:1:2:3:4:5:6, получится такой адрес Г: FF02::1:FF05:6. Общепринятый термин для внутриканального группового адреса, составленного по этому рецепту, — групповой адрес искомого узла (solicited-node multicast address).
Таково общепринятое английское название, тогда как русскоязычная терминология IPv6 еще только формируется. Обнаруженный нами
у других
Теперь мы знаем, по какому адресу надо слать запрос о розыске соседа. Это адрес группы искомого узла, объединяющей все интерфейсы канала с индивидуальными адресами, у которых младшие 24 бита такие же, как в искомом адресе. Хотя в такую группу могут попасть несколько интерфейсов с разными адресами, розыск соседей становится значительно прицельнее, нежели при широковещании или рассылке по групповому адресу "все узлы". Конечно, теперь всякий узел IPv6, назначая своему интерфейсу индивидуальный адрес, обязан вступить на этом интерфейсе в соответствующую группу искомого узла, чтобы принимать запросы от соседей. Когда же адрес удаляется, узел должен выйти из этой группы, если только на интерфейсе не остается других адресов с теми же 24-мя младшими битами. На этом наша первая задача решена.
На первый взгляд, число групп искомого узла, в которые узлу надо вступить на данном интерфейсе, равно числу индивидуальных адресов на этом интерфейсе. Но чуть более внимательный анализ обнаруживает известное нам свойство: разные индивидуальные адреса отображаются в один и тот же групповой адрес искомого узла, если в них совпадают 24 младших бита. Таким образом, аккуратный выбор идентификаторов интерфейса может сократить число групп искомого узла, которые придется слушать. Простейший подход сводится к тому, чтобы все идентификаторы интерфейса сделать одинаковыми, например, основанными на модифицированном EUI-64 этого интерфейса. Тогда адреса IPv6 этого интерфейса будут отличаться только префиксами подсети, а их группы искомого узла заведомо совпадут. Почему число таких групп стоит ограничивать? Во-первых, как мы сказали на с. 103, группы расходуют ресурсы сети. А во-вторых, современные сетевые адаптеры могут сами фильтровать групповой трафик, чтобы освободить шину и центральный процессор, но длина аппаратного фильтра в них ограничена. Когда узел слушает много разных групп и длина такого фильтра оказывается превышена, адаптер вынужден принимать весь групповой трафик в надежде, что центральный процессор разберется.
Группа искомого узла — это лишь частный случай применения группового вещания IPv6 для направленного розыска сетевых ресурсов. В общем случае, узел, обладающий ресурсом, вычисляет групповой адрес как функцию от имени ресурса и вступает в эту группу, а узел-искатель, зная имя ресурса, проводит то же самое вычисление и направляет по полученному групповому адресу запрос. К примеру, так можно разыскивать хост по его символьному имени: от имени хоста в заданной кодировке вычисляют хэш-функцию подходящей длины и добавляют к ее значению общепринятый групповой префикс. На этой идее уже основан один экспериментальный протокол для запроса информации об узлах IPv6 [RFC 4620].
Следующий вопрос заключается в том, как будет устроен протокол розыска соседей. Пока что мы не определились даже с его уровнем в стеке. Привлечение групповых адресов сетевого уровня означает, что новый протокол мы должны разместить над IPv6, а не рядом с ним, в отличие от IPv4 и ARP. По сути, розыск соседей — это один из аспектов управления IPv6, так что давайте поступим просто: пусть розыск соседей станет подмножеством ICMPv6. Это подмножество кратко обозначают ND (от neighbor discovery) [RFC 4861].
Как следствие, сообщения ND будут инкапсулированы в пакеты IPv6, а тогда на них смогут распространяться пакетные фильтры и прочие правила сетевой политики. Поэтому придется быть вдвойне осторожным, чтобы по ошибке не блокировать легитимный трафик ND и тем самым не "сломать" механизм разрешения адресов — ведь от его сбоя пострадает передача всего индивидуального трафика!
Такая позиция ND в стеке протоколов позволит применять его на любых каналах, способных инкапсулировать IPv6. Хотя сам по себе розыск соседей необходим далеко не всем типам каналов, например, он явно избыточен на каналах "точка-точка", мы создадим механизмы на основе протокола ND, которые будут уместны независимо от типа канала. Поэтому сразу выдвинем вот какое положение: ND будет работоспособен на всех каналах IPv6, которые поддерживают групповое вещание [§3.2 RFC 4861].
Означает ли наш выбор, что сообщения ND смогут пересекать границы каналов? Ведь в общем случае пакеты ICMPv6 подлежат маршрутизации наравне с другими пакетами IPv6. Между тем, из самого определения соседства следует, что пакеты ND должны распространяться только в пределах одного канала. Опасность здесь не только в утечке ND за границы канала, но и в проникновении фальшивых сообщений ND из-за этих границ.
Сможет ли адресат пакета ND узнать, что источник пакета — не его сосед? Если такой адресат не доверяет никому, то задача без дополнительных механизмов неразрешима. Но если адресат доверяет маршрутизаторам данного канала, то он может учесть, что всякий законопослушный маршрутизатор уменьшает значение "предельное число шагов" в заголовке IPv6 транзитного пакета. Сами по себе эти сведения еще ничем не помогают, так как адресат не знает, каким было начальное значение этого поля. Можем ли мы просто зафиксировать это значение? Допустим, мы решили, что начальное значение "предельного числа шагов" в пакетах ND всегда будет X. Теперь адресат пакета сравнивает видимое им значение с X и, если они совпадают, заключает, что пакет пришел от соседа. Но злоумышленник может как-нибудь узнать число шагов H до атакуемого узла и установить поле "предельное число шагов" равным , если отвечает соседству. Как помешать злоумышленнику? Если школьная арифметика помогла ему, то она поможет и нам! По условию задачи, , так как злоумышленник — не сосед атакуемого узла. (Если бы он оказался соседом, узлу пришлось бы совсем несладко!) Значит, злоумышленнику придется увеличить значение "предельного числа шагов" относительно X: . Единственный случай, когда он не сможет этого сделать, — это если значение X уже будет максимально возможным, а именно 255.
Этот трюк работает, только если маршрутизаторы правильно обрабатывают пакеты с нулевым значением "предельного числа шагов", как мы обсудили в §3.2. В противном случае декремент этого значения даст 255 согласно беззнаковой арифметике по модулю 256.
Атаки на ND со стороны соседей нам еще предстоит рассмотреть.
Обнаруженный нами трюк способен защитить от дистанционных атак не только ND и не только в среде IPv6. Ведь все, что ему нужно, — это поле "предельное число шагов" или "TTL" в сетевом заголовке. За свою универсальность этот трюк получил пышноватое название "обобщенный механизм безопасности на основе TTL" (Generalized TTL Security Mechanism), сокращенно GTSM [RFC 5082].
Другой пример протокола, который может выиграть от применения GTSM, — это BGP. Исторически идея GTSM появилась именно в применении к BGP [§4 RFC 5082], а затем ее обобщили и на другие случаи.
Конечно, GTSM не заменяет собой прочие механизмы сетевой безопасности, такие как фильтрация пакетов на границах канала. Просто любая хорошо построенная оборона обязана быть глубоко эшелонированной.
Итак, участники ND обязаны применять GTSM: источник пакета ND должен установить поле "предельное число шагов" равным 255, а его адресат обязан убедиться, что это значение сохранилось [§6.1, §7.1, §8.1 RFC 4861].
Теперь можно перейти к отдельным сообщениям ND. Чтобы провести разрешение сетевого адреса, достаточно двух видов сообщений, которые в первом приближении выглядят как запрос и ответ. Но, как мы помним, даже в ARP строгую последовательность "запрос-ответ" приходилось нарушать, когда узлу надо было послать "добровольный ARP" (gratuitous ARP), чтобы самому объявить соседям свой канальный адрес.
В ARP не имело значения, представлял ли собой добровольный ARP запрос или ответ, потому что алгоритм получателя ARP [RFC 826] проверял код операции только после того, как обновлял локальный кэш.
Не Конфуций ли сказал: если название неправильное, то и слова не повинуются? Поэтому давайте для начала исправим терминологию. Сообщение, которое несет сведения о сетевом и канальном адресах узла, мы назовем объявлением соседа (neighbor advertisement), сокращенно NA. Узел вправе выслать это объявление добровольно или по запросу. Другие же узлы могут запросить такое объявление, когда им это нужно, послав вызов соседа (neighbor solicitation), сокращенно NS.
Снова представим себе, что узел А хочет передать пакет своему соседу Б, но не знает его канального адреса. Теперь у узла А есть простая методика:
А что узел А должен делать после? Если он немедленно забудет канальный адрес Б, то указанную процедуру ему придется повторять на каждый пакет для соседа Б, а это будет совершенно неэффективно. С другой стороны, узел А не должен хранить канальный адрес Б в своей памяти вечно, потому что тот может впоследствии измениться, например, если в узле Б "на лету" сменят сетевой адаптер.
Подумайте, какая из возникших проблем была бы самой острой, если бы процедуру розыска соседа пришлось бы выполнять для каждого исходящего пакета? (Авторский вариант: возникновение задержки, равной ПКО (RTT) данного канала.)
Нашего опыта достаточно, чтобы сказать: здесь нужен кэш. Действительно, кэш соответствия между сетевыми и канальными адресами уже был в ARP. Политика управления им была весьма простой [RFC 826]:
В отсутствие дополнительных сообщений ARP, запись неизбежно удалялась по тайм-ауту, даже если тем временем протокол более высокого уровня, например, TCP, успешно вел обмен данными с узлом Б, чем подтверждал актуальность данной записи. Хуже того, при активном обмене данными с узлом Б широковещательная процедура ARP немедленно повторялась после удаления его записи, создавая паразитную нагрузку на других соседей. Так было потому, что между вышестоящими уровнями стека и ARP отсутствовала обратная связь.
Этот неоптимальный подход появился с одобрения [§2.3.2.1 RFC 1122] — см. п. 1. Хотя в том же разделе RFC были предложены и более
эффективные приемы работы с кэшем ARP, п. 1 распространился благодаря сетевому стеку BSD
Конечно же, уровни сетевого стека выше IP, например, транспортный уровень SCTP, TCP и UDP, обязаны быть только у хоста, тогда как маршрутизатору в теории достаточно продвигать транзитные пакеты IP.
Мы обязательно примем меры, чтобы ND не унаследовал этот недостаток. Сам же кэш канальных адресов был важной и полезной деталью; он нам необходим и сейчас. Назовем мы его просто: кэш соседей (neighbor cache), сокращенно NC.
Работу над NC мы начнем именно с того, что постулируем связь между ND и вышележащими уровнями в сетевом стеке хоста IPv6. Эта связь носит локальный характер и не выходит за пределы одного узла, поэтому ее можно реализовать по-разному. В простейшем случае, это будет просто вызов функции ядра. Главное, что теперь более высокий уровень в стеке способен сообщить модулю ND: да, я уверен, что данный адресат доступен. Например, если этим уровнем будет TCP, то он сможет утверждать доступность адресата, если от того поступят новые данные или новые квитанции (не дубликаты).
На первый взгляд, TCP делает необоснованный вывод; ведь прием пакетов вовсе не означает, что мы знаем верный канальный адрес их передатчика. Однако TCP — это протокол с обоюдным квитированием, и приход новых данных или квитанций возможен, только если удаленная сторона успешно получает наши квитанции или данные, соответственно.
Подобную обратную связь можно было реализовать и в ARP. П. 4 §2.3.2.1 RFC 1122 вполне допускает связь между вышележащими уровнями и модулем ARP, хотя и говорит о передаче отрицательных, а не положительных сигналов: протокол более высокого уровня сообщает модулю ARP, что данный узел недоступен, а модуль ARP удаляет запись об этом узле.
Подробному обсуждению тонкостей обратной связи между вышестоящими протоколами и ND посвящено [Приложение E.1 RFC 4861].
Однако у нас есть одно затруднение: вышестоящий уровень подтверждает доступность адресата, а не соседа. Это вполне закономерно: ведь выбор соседа происходит на уровне IP, в процессе маршрутизации исходящего пакета. Поэтому мы должны обеспечить логический переход от адресата к соседу.
Здесь под маршрутизацией мы понимаем выбор следующего шага пакета, а не продвижение транзитных пакетов (forwarding). В этом смысле маршрутизировать надо все исходящие пакеты, включая созданные на данном узле.
Если адресат окажется соседом локального узла, то модуль ND может сразу же обновить его запись NC. Но если адресат доступен только косвенно, через маршрутизатор, то перед модулем ND встанет такой вопрос: через какого соседа мы последний раз слали пакет данному адресату? Ответить на него поможет еще один кэш, который станет хранить самые свежие пары "адресат-сосед". Он так и называется: кэш адресатов (destination cache), кратко DC.
Также запись в кэше адресатов может хранить дополнительные сведения о данном адресате, например, текущую оценку величины PMTU. К этой идее мы придем естественным образом в §6.5.
Хотя модуль ND маршрутизатора не получает сигналов от вышележащих уровней стека по причине отсутствия оных, кэш адресатов может пригодиться и маршрутизатору. Например, при распределении трафика по равноценным путям (equal-cost multipath, ECMP) важно, чтобы все пакеты одного прикладного потока, такого как сеанс TCP, направлялись по одному пути. В противном случае в сети могут возникнуть трудно диагностируемые проблемы [RFC 2991]. Самая распространенная из них — это переупорядочение пакетов из-за того, что задержки разных путей всегда немного отличаются. Конечно, переупорядочение не фатально для TCP/IP, но, происходя постоянно, оно значительно понижает эффективность транспортных протоколов. Например, для механизмов управления перегрузкой TCP приход пакета вне очереди — это признак потери предыдущего пакета, а значит, перегрузки в сети [§3.2 RFC 5681]. Еще одна серьезная проблема — это видимые флуктуации PMTU, если PMTU вдоль разных путей отличается. Кэш адресатов поможет направить все потоки одного адресата по одному и тому же пути.
Чтобы нам стало понятнее, как возникшие структуры данных связаны между собой, приведем иллюстрацию — рис. 5.3. В качестве отправной точки мы включили в эту схему хорошо знакомую нам таблицу маршрутов. Однако давайте иметь в виду, что в §5.2 мы пересмотрим эталонную модель хоста IPv6 и придем к неожиданному выводу, что таблица маршрутов для него необязательна. С другой стороны, программная реализация может совместить все три логических структуры в одной [§5.1 RFC 4861], например, в базисном дереве (radix tree) по образу и подобию таблицы маршрутов BSD.
(рис 5.3) Связь между структурами данных в памяти узла: таблица маршрутов, DC и NC
Мы еще обсудим кэш адресатов в §5.2, а сейчас перейдем к структуре и жизненному циклу записи NC. Сама роль такой записи требует, чтобы ключом в ней выступил адрес IPv6 соседа; то есть в NC не может быть двух записей об одном и том же соседском адресе. В то же время, механизм ND даже не пытается выяснить, принадлежат ли разные адреса IPv6 одному соседскому интерфейсу или узлу, и поэтому, говоря "запись NC о соседе Б", мы подразумеваем запись об индивидуальном адресе Б, ставя условный знак равенства между узлом-соседом и его индивидуальным адресом. Это одно из допущений модели ND, которое, не будучи универсальной истиной, вполне работает в частном случае разрешения индивидуальных адресов IP в канальные адреса.
Говоря об использовании множественных подключений в §6.8, мы встретим случай, в котором принципиально важно учитывать, какие адреса IPv6 принадлежат одному и тому же удаленному узлу. Однако тот случай не будет иметь прямого отношения к розыску соседей.
В своем исходном состоянии запись NC о соседе Б просто не существует. Ее небытие продолжается до тех пор, пока у локального узла А впервые не возникнет необходимость передать пакет соседу Б.
Обратите внимание, как это отличается от поведения ARP. В кэше ARP запись создавалась по приходу любого сообщения ARP, чей сетевой адрес цели (target protocol address) принадлежал локальному узлу. Поэтому в кэше ARP могли возникать ненужные записи. Напротив, входящее объявление NA никогда не служит поводом создать новую запись NC.
Тогда узел А создает запись, но канальный адрес в ней еще неизвестен. Это состояние НЕПОЛНАЯ (INCOMPLETE). Чтобы
заполнить запись, узел А должен получить от Б объявление соседа. О своем желании он сообщает, направляя вызов соседа на групповой
адрес искомого узла, полученный из индивидуального адреса "Б". Дальше узлу А остается только ожидать. Если ожидание затянется
Для этого служит сообщение ICMPv6 тип 1, код 3 [§3.1 RFC 4443].
У записей в кэше ARP тоже было неполное состояние — по крайней мере, начиная с сетевого стека
Но вот, допустим, от узла Б своевременно поступило объявление соседа. Значит ли это, что сосед Б доступен и его канальный адрес можно с уверенностью использовать? Строго говоря, нет. Ведь у нас нет гарантии, что это объявление выслано в ответ на вызов узла А. Может быть, вызовы не доходят до Б из-за одностороннего сбоя в канале, но Б сам почему-то решил выслать объявление NA (ему это не возбраняется). Надежно различить эти два случая можно, если формат NA предусмотрит специальный флаг. Скажем, если он сброшен, то объявление добровольное, а если установлен — то по вызову. Обозначим этот флаг как S (solicited, по вызову).
По протоколу, узел Б устанавливает флаг S, только если сообщение NA адресовано персонально узлу А в ответ на запрос NS узла А. Именно эту информацию флаг S и доносит до узла А. Поэтому дополнительные проверки со стороны узла А, как то сверка адреса назначения NA, в этом случае избыточны.
Еще раз обратим внимание: хотя приход "непрошенного" объявления NA — это вполне допустимое событие, оно не приводит к созданию новой записи NC, а только влияет на уже существующую запись.
Теперь, в зависимости от состояния флага S в полученном NA, перед узлом А открываются два пути. Если флаг установлен, то канал между А и Б явно работает в двух направлениях, и записью NC можно смело пользоваться хотя бы некоторое время. В нее надо поместить канальный адрес Б, взятый из NA, и перевести запись в состояние ДОСТУПНАЯ (REACHABLE).
Какое время запись NC будет оставаться ДОСТУПНОЙ в отсутствие дополнительных сигналов? Чтобы избежать синхронизации соседей
между собой, это время надо сделать разным на разных узлах, например, случайным
Если же флаг S сброшен, то о доступности узла Б по-прежнему ничего не известно, но узел А все-таки узнал канальный адрес Б и может рискнуть передать по нему ожидающий пакет в канальном обрамлении. В этом случае узел А тоже сохраняет канальный адрес Б в записи NC, чтобы информация не пропала зря, но переводит запись в состояние ПРОСРОЧЕННАЯ (STALE), на котором мы остановимся подробнее буквально через несколько абзацев. В двух словах, сведения из ПРОСРОЧЕННОЙ записи имеют хороший шанс оказаться актуальными , но, тем не менее, нуждаются в подтверждении, чтобы запись стала ДОСТУПНОЙ.
Все состояния, в которых запись заполнена (в ней указан не только сетевой, но и канальный адрес), мы будем собирательно называть полными.
ДОСТУПНАЯ запись освежается по сигналу от вышестоящих протоколов, или же дополнительными объявлениями NA с S = 1 —
то есть сбрасывается тайм-аут записи. Но допустим, что такую запись
Так как в ND активная ДОСТУПНАЯ запись постоянно освежается, ее тайм-аут может быть существенно меньше, чем знакомое нам время жизни записи в кэше ARP. Так что не стоит удивляться, что простаивающие ДОСТУПНЫЕ записи NC становятся ПРОСРОЧЕННЫМИ гораздо скорее, нежели предсказывает наш опыт IPv4.
ПРОСРОЧЕННАЯ запись хранит сведения, в которых узел А не уверен. Без проверки пользоваться ими нежелательно, и поэтому запись остается ПРОСРОЧЕННОЙ ровно до тех пор, пока к ней не будет обращений. Но, с другой стороны, и обязательного тайм-аута у ПРОСРОЧЕННОЙ записи нет, потому что она содержит полезные сведения.
Проходит время, и вот узел А опять готов передать новый пакет соседу Б, запись о котором ПРОСРОЧЕННАЯ. Весьма вероятно, что
узлы А и Б находятся там же, где они были и раньше. Поэтому узел А шлет пакет по канальному адресу Б, взятому из записи. Он надеется,
что вскоре вышестоящий протокол подаст сигнал о доступности соседа Б. Такое событие узел А отмечает, переводя запись в состояние
ЗАДЕРЖКА (DELAY) и устанавливая ее таймер
А что если интервал этого таймера истек, а сигнала сверху по-прежнему нет? Даже тогда для записи в состоянии ЗАДЕРЖКА не все
потеряно: ведь, возможно, переданный пакет не предполагал никакого отклика, или он потерялся где-то дальше в сети. Чтобы разрешить
возникшую неоднозначность, узел А целенаправленно проверяет годность записи NC: он направляет вызов NS по индивидуальному адресу
узла Б, используя канальный адрес из записи, и переводит запись в состояние ИСПЫТАНИЕ (PROBE). И, наконец, если даже
после нескольких
Современные реализации IPv4 тоже освежают записи в кэше ARP по истечении тайм-аута, индивидуально обращаясь к соседям. Например, так
поступает ядро
Окончательная хронология не обновляемой и потому обреченной исчезнуть записи NC показана на рис 5.4(рис 5.4) Наглядная хронология устаревающей записи NC
Пока запись NC полная, но проходит проверку, в очереди узла А могут возникнуть прикладные пакеты, требующие передачи соседу Б. Должен ли узел А приостановить их отправку до тех пор, пока он не убедится в достоверности записи? На самом деле, пауза здесь не требуется и может быть даже вредна, так что узел А продолжает передавать пакеты соседу Б, используя канальный адрес из записи. Благодаря этому, у вышестоящих протоколов тоже появляется возможность освежить запись. Сигнал вышестоящего протокола переводит полную запись из любого состояния в состояние ДОСТУПНАЯ. Очевидно, что на несуществующие и НЕПОЛНЫЕ записи сигнал не действует, даже если он вдруг поступит, поскольку в таких записях нет информации о канальном адресе соседа.
Хотя начинали мы с простой экономии ресурсов канала и узлов-соседей, в результате у нас получился полнофункциональный механизм наблюдения
за состоянием соседей, которым локальный узел передает пакеты. Этот механизм называют выявление недоступности соседей
(neighbor unreachability detection), сокращенно NUD [§7.3 RFC 4861]. По сути, механизм NUD — это и есть конечный
автомат ND, а информация о доступности соседей — один из основных продуктов его работы. На текущий момент у нас готов упрощенный вариант
этого механизма, схематически показанный на рис 5.5(рис 5.5) Упрощенная диаграмма состояний ND
Более полную диаграмму состояний ND можно найти, например,
Ценой определенных усилий мы победили в ND недостатки ARP и теперь можем спокойно взглянуть на плодотворные идеи старого протокола, чтобы не потерять их в ND.
Первая идея состоит в том, что большинство запросов ARP, а теперь и вызовов NS открывает некий сетевой диалог. Иными словами, очень часто узел А проводит розыск соседа Б и передает ему пакет, а вскоре уже узел Б имеет что передать своему соседу А. Если А и Б — хосты, то это естественный стиль работы большинства прикладных протоколов. Если же один из них или оба — маршрутизаторы, то здесь проявляется не только двунаправленная работа прикладных протоколов, но и практическая симметрия маршрутов. Поэтому модуль ARP, получив запрос, предусмотрительно создавал запись о его источнике, если запрос касался локального узла: он предвидел, что скоро роли поменяются и источник запроса станет искомым соседом, получателем пакета IP.
В среде IPv6 намек на предстоящий диалог — это вызов NS. Если формат сообщения NS предусмотрит место для канального адреса источника (А), то получатель NS (Б) сможет сразу же создать о нем полную запись NC. По правилам NUD, эта запись — ненадежная, потому что проверена только односторонняя проходимость канала: А$$\to$$Б. Значит, такая запись заслуживает состояния ПРОСРОЧЕННАЯ. Это состояние записи вполне позволит сетевому диалогу начаться без задержки на еще один цикл ND между А и Б, когда узел Б станет разыскивать соседа А. Далее , если все будет в порядке, запись о соседе А перейдет в состояние ДОСТУПНАЯ по стандартной процедуре, не задерживая диалог.
Вторая идея заключается в том, что на розыск соседа не всегда откликается сам владелец искомого сетевого адреса. Искомый узел может находиться вне канала, а ответит за него маршрутизатор, у которого есть сведения о доступности искомого узла, как то маршрут к нему, более точный (specific), чем префикс подсети канала. Поскольку такой маршрутизатор отвечает от имени искомого узла и выступает посредником между искателем и искомым, то его обозначают популярным термином proxy. В частности, в ARP такой маршрутизатор назывался ARP proxy. Задача подобного proxy — привлечь к себе трафик для искомого узла, чтобы затем продвинуть его согласно таблице маршрутов, как показано на рис. 5.6. Поэтому proxy должен ответить, что искомому сетевому адресу отвечает его собственный канальный адрес. Особенность IPv6 здесь только в том, что ND proxy должен вступить в группу искомого узла, отвечающую адресу "Б", чтобы принимать вызовы NS к Б и отвечать на них объявлениями NA от имени Б.
(рис 5.6) Идея: ND proxy отвечает за другой узел
Если ND proxy отвечает от имени многих узлов, например, целой подсети, ему может пригодиться режим работы сетевого интерфейса, когда тот принимает все групповые кадры подряд (allmulti). Однако это может вызвать осложнения в коммутируемой ЛВС, использующей сообщения IGMP или MLD (§6.4) для изучения географии групп IP. В такой ЛВС трафик группы просто не дойдет до портов, из которых не поступало уведомления о вступлении в данную группу.
Функция ND proxy должна быть настраиваемой. Как минимум, ей требуется "выключатель", потому что по умолчанию она должна быть выключена.
А как быть, если искомый узел — мобильный и может находиться то по соседству, то вне данного канала, не меняя при этом своего адреса? Сохраним наши обозначения: пусть Б — это искомый узел, тогда как А — наш локальный узел, который хочет передать пакет непосредственно узлу Б, полагая при этом, что Б — его сосед. Этот сценарий представлен на рис. 5.7.
(рис 5.7) Мобильность узла, за которого отвечает ND proxy
Пусть сначала узел Б находится в другой части сети. Узел А начинает розыск узла Б запросом ARP в IPv4 или вызовом NS в IPv6. От имени узла Б отвечает proxy и сообщает свой канальный адрес. Узел А думает, что это ответил его сосед Б, и передает пакет по канальному адресу proxy. Так proxy перехватывает пакеты, предназначенные узлу Б, и поступает с ними согласно своим текущим настройкам. В это время узел Б вообще не видит запросов ARP или вызовов NS, которые посылает узел А, потому что узлы А и Б на самом деле не являются соседями.
Теперь допустим, что узел Б незаметно перемещается на данный канал и действительно становится соседом узла А, как показано на рис. 5.8. В этом случае он уже самостоятельно отвечает на запросы (вызовы) узла А. Но proxy тем временем продолжает отвечать от его имени, потому что ничего не знает о перемещении узла Б. Это значит, что на каждый запрос узла А придет два ответа (объявления), сообщающих разные канальные адреса искомого узла Б! Какой окажется запись об узле Б в кэше узла А, если он успешно получит оба ответа? В ARP все решает ее величество фортуна: окончательный вид записи зависит от порядка, в котором поступят ответы, а он, можно сказать, случайный. "Победит" ответ, пришедший последним: именно его канальный адрес останется в записи. Таким образом, примерно в половине случаев в запись об узле Б попадет канальный адрес proxy, и пакеты пойдут туда, где узла Б в данный момент нет. Проблема налицо, и нам обязательно надо исправить ее в ND, потому что сегодня мобильные узлы получают все большее распространение.
Точнее, в IPv4 один или несколько первых пакетов пойдут по канальному адресу из ответа ARP, пришедшего первым, а затем придет другой ответ, и последующие пакеты пойдут по канальному адресу из него.
(рис 5.8) Мобильность приводит к сбою: трафик уходит не туда
К счастью, перед нами всего два уровня в ранжире узлов, "борющихся" за искомый адрес. Первый, с наибольшим приоритетом — это действительный владелец адреса Б. По определению индивидуального адреса, владелец у него ровно один. Второй уровень — это proxy, которых может быть и несколько. Относительный приоритет одних proxy перед другими мы определять не будем, так как в данном случае proxy — это вспомогательный механизм, и его не стоит усложнять сверх меры. Поэтому нам достаточно одного бита в формате NA, который и укажет, пришло ли объявление NA от владельца адреса или от proxy.
Теперь давайте подумаем, как получатель обязан реагировать на то или иное значение этого бита, чтобы получился желаемый результат. Нам необходимо, чтобы в итоге "выигрывал" владелец адреса, независимо от последовательности объявлений:
То есть объявление владельца вытесняет данные, полученные от proxy, но не наоборот. Обеспечить это можно было бы, снабдив запись в NC атрибутом: заполнена ли она по объявлению владельца или proxy. Но нельзя ли обойтись без дополнительного атрибута? Если объявление послал владелец, то оно безусловно обновляет запись в кэше, и происхождение этой записи неважно. Если же объявление исходит от proxy, а полная запись с другим канальным адресом уже существует в NC, то достаточно воспользоваться конечным автоматом NUD и просто перевести запись в состояние ПРОСРОЧЕННАЯ, не изменяя других ее полей.
Пожалуйста, не забывайте, что, в отличие от ARP, запись в кэше соседей ND никогда не создают по приходу объявления NA. Объявление NA может повлиять только на уже существующую запись, будь то полную или НЕПОЛНУЮ.
Став ПРОСРОЧЕННОЙ, запись проходит процедуру проверки. Если указанный в ней канальный адрес по-прежнему доступен, то запись просто возвращается в состояние ДОСТУПНАЯ. Так произойдет, если запись указывает на "живого" владельца искомого адреса, или же на "живой" proxy, который предлагает доступ к владельцу искомого адреса. В противном случае запись исчезнет, а узел А повторит процедуру ND с самого начала. Это случится, в частности, когда узел Б снова уйдет с канала и станет доступен только через proxy.
Поскольку все proxy равноправны, в рамках ND нет способа определить, какой именно proxy дает доступ к узлу Б, когда тот вне канала и за него отвечают несколько proxy. Эту проблему придется решать надлежащей настройкой самих proxy.
Обратите внимание: механизм NUD следит только за состоянием подлинных соседей. Так, если запись NC об узле Б указывает на proxy, то ее состояние — это состояние proxy, а не самого узла Б. О доступности узла Б в этом случае NUD информации не предоставляет, потому что он ограничен рамками канала и не рассчитан на слежение за сквозной доступностью. Эту архитектурную особенность стоит иметь в виду, даже если вы не собираетесь применять ND proxy на практике.
Окончательный смысл нового флага в сообщении NA сводится к тому, замещать ли уже существующую полную запись. Поэтому данный флаг мы обозначим как "замещение" (override), сокращенно O. Если в принятом сообщении NA флаг O установлен (O = 1), то сведения из этого сообщения имеют приоритет перед записью в NC. Канальный адрес из сообщения немедленно перемещается в запись. Сама запись становится ДОСТУПНОЙ, если сообщение по вызову (S = 1), или ПРОСРОЧЕННОЙ, если оно добровольное (S = 0). Если же флаг O сброшен (O = 0), то новый канальный адрес в сообщении NA не приводит к немедленному обновлению записи NC. Вместо этого ДОСТУПНАЯ запись просто становится ПРОСРОЧЕННОЙ; в других полных состояниях запись вообще остается без изменений, потому что она уже проходит проверку [§7.2.5 RFC 4861]. После дополнения этими правилами конечный автомат NUD приобретает вид, показанный на рис. 5.9.
(рис 5.9) Диаграмма состояний ND с учетом флага O и смены канального адреса
Теперь, благодаря флагу O и связанным с ним правилам обновления канального адреса, мы можем отразить в ND еще одну полезную идею ARP. Если у интерфейса данного узла менялся канальный адрес, то узел высылал широковещательный "добровольный ARP", в котором разыскивал собственный сетевой адрес или отвечал о нем сам себе. Это вызывало немедленное обновление существующих записей у его соседей — точнее, у тех из них, до кого добровольный ARP успешно дошел. Почему происходило обновление, мы уже обсудили: по правилам ARP, получатель сообщения первым делом искал сетевой адрес источника в своем кэше и обновлял его канальный адрес [RFC 826]. Хотя этот механизм вполне работал, сейчас давайте позаботимся, чтобы каждый тип сообщения ND делал, по возможности, свое дело. К счастью, узел IPv6 уже вправе выслать объявление NA, когда ему этого захочется. Единственная обязанность узла, когда объявление добровольное, — это сбросить в нем флаг S.
Добровольный запрос ARP служил также и для того, чтобы обнаруживать конфликты адресов IP, но об этой функции мы поговорим в §5.4.1.
Поэтому, когда происходит смена канального адреса, узел IPv6 может выслать добровольное объявление NA о своем новом канальном адресе.
Подходящими флагами в этом объявлении будут сброшенный флаг S (S = 0) и установленный флаг O (O
= 1), поскольку объявление не по запросу, однако его источник самый авторитетный. А по какому адресу следует выслать такое объявление?
Так как данный узел не знает, кто из его соседей хранит в NC запись о нем, объявление придется выслать по групповому адресу "все
узлы канала", FF02::1. Чтобы оно наверняка не потерялось, его можно повторить несколько раз
На этом мы исчерпали первую порцию задач, связанных с розыском соседей в IPv6, и можем наконец-то собрать вместе требования к формату сообщений NS и NA.
По нашему плану, центральный элемент сообщения NS — это адрес IPv6 искомого соседа, о котором происходит запрос. Нам уже сейчас нетрудно предвидеть, что сетевой или канальный адрес соседа, о котором идет речь в данном сообщении, будет элементом в формате многих сообщений ND. Поэтому давайте называть его одним термином: адрес цели (target address). В сообщении NS (рис. 5.10) центральное место займет сетевой адрес цели, то есть ее адрес IPv6 [§4.3 RFC 4861].
Флаги в сообщении NS нам не понадобились. Зато выше, на с. 112, мы запланировали, что в целях оптимизации сообщение NS может включать в себя канальный адрес источника (при этом сетевой адрес источника находится в заголовке IPv6). Насколько эта информация необходима в теле NS и есть ли у нее другие применения?
Мы настолько сконцентрировались на поведении узла-искателя, что совсем забыли об искомом узле и упустили из виду вот какую важную деталь: чтобы ответить индивидуальным объявлением NA на вызов NS, необходимо сперва узнать канальный адрес узла-искателя. Очевидно, что применить ND для этой цели нельзя, так как протокол зациклится и возникнет тупиковая ситуация. Поэтому верным решением будет указать канальный адрес узла-искателя, источника NS, непосредственно в самом NS. Конечно, канальный кадр, доставивший вызов NS, должен содержать в себе адрес источника, но с уровня IP доступ к канальному заголовку может быть затруднен ввиду программных ограничений стека. Поэтому вполне оправдано повторить канальный адрес источника в теле вызова NS.
Таким образом, получается, что типичное сообщение NS не только может, но и должно содержать в себе канальный адрес источника. Из этого правила будет всего одно серьезное исключение. Когда мы будем говорить в §5.4 об автоматической настройке адресов, у нас возникнет вызов NS с неопределенного адреса и без канального адреса источника. Уже и сейчас понятно, что такой особенный вызов NS понадобится, чтобы обойти очередную "проблему курицы и яйца" на нашем пути.
Еще есть один случай, когда канальный адрес источника в вызове NS рекомендован, но не обязателен: когда вызов NS индивидуальный [§4.3 RFC 4861]. Ведь такой вызов шлют соседу, когда его запись NC уже существует в заполненном виде, а значит, велик шанс, что у соседа тоже есть запись NC о нашем локальном узле [§7.2.2 RFC 4861]. Сосед должен был создать такую запись по приходу от нас самого первого вызова NS, который был групповым и содержал канальный адрес источника.
В итоге, канальный адрес источника — это не вполне обязательный элемент NS, а значит, нам понадобятся опции в формате ND.
(рис 5.10) Вызов соседа — NS
Пусть общий вид опций ND (рис. 5.11) будет основан на уже знакомом нам формате TLV: тип (1 байт), длина (1 байт), а затем переменное количество данных, формат которых зависит от типа [§4.6 RFC 4861]. Длина опции ND измеряется в 8 байтных словах и, разнообразия ради, включает в себя байты типа и длины, так что нулевое значение длины свидетельствует о сбое. Узнать о наличии опций в сообщении ND можно, определив, где находится его конец: если он совпадает с концом фиксированной части формата, то опций нет, а иначе они присутствуют. Как определить границы сообщения ND, мы уже сказали в §4.3; ведь ND — это подмножество ICMPv6.
(рис 5.11) Общий вид опции ND
В уже потребовавшейся нам опции "канальный адрес источника" (Source Link-Layer Address Option, SLLA или SLLAO) роль данных играет канальный адрес, а ее тип равен 1 [§4.6.1 RFC 4861]. Такой формат опции SLLA представлен на рис. 5.13. В частности, на канале Ethernet длина данных будет равна шести байтам, а всей опции — единице (8 байт), как показано на рис. 5.10. Поэтому, к примеру, адрес MAC 02 03 04 05 06 07 будет инкапсулирован в эту опцию так: <0x01, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07>.
Предполагается, что у каждой канальной технологии есть стандарт RFC, где зафиксированы детали работы IPv6 поверх каналов этого типа. Точный формат опций ND "канальный адрес" — одна из таких деталей. Например, см. [§6 RFC 2464] для Ethernet.
В отличие от ARP, ND не указывает явным образом тип канального адреса. Ведь он однозначно следует из типа канала,
откуда пришло сообщение ND.
(рис 5.12) Опции SLLA (тип = 1) и TLLA (тип = 2) — общий формат
(рис 5.13) Опции SLLA (тип = 1) и TLLA (тип = 2) для Ethernet
Сообщение NA содержит сведения о соседе, а именно его сетевой и канальный адреса. Согласно нашей терминологии, это адреса цели. Хотя сетевой адрес источника сообщения приведен в заголовке IPv6, он может и не совпадать с сетевым адресом цели, так как сообщение мог послать proxy, а не сам искомый узел. Чтобы proxy не пришлось "подделывать" адрес в заголовке IPv6, мы поместим сетевой адрес цели отдельно, в тело сообщения NA [§4.4 RFC 4861].
Форматы NS и NA могут стать довольно похожими, если мы приложим к этому немного усилий ради упрощения будущих реализаций. Для этого пусть канальный адрес цели разместится в опции, а не в фиксированной части сообщения NA. Когда искомый узел или proxy отвечает на групповой вызов NS, он обязан включить эту опцию в объявление NA, чтобы сообщить канальный адрес цели. В то же время, когда узел отвечает на индивидуальный запрос NS, он вправе предположить, что источник запроса уже располагает правильным канальным адресом цели; в этом случае канальный адрес цели в объявлении NA можно опустить, хотя особой выгоды это не принесет. Соответствующая опция — "канальный адрес цели" (Target Link-Layer Address Option, TLLA или TLLAO) — отличается от опции "канальный адрес источника" только типом, который равен 2. Благодаря этому, формат обеих опций можно представить на одной иллюстрации; их общий формат показан нарис. 5.12, а формат для Ethernet — на рис. 5.13.
Кроме того, в сообщении NA мы запланировали несколько флагов. Помимо уже знакомых нам флагов S и O, мы отведем бит для флага R, необходимость в котором возникнет ниже, в §5.4.2. Окончательный формат NA будет таким, как показано на рис 5.14(рис 5.14) Объявление соседа — NA
Прекрасно, теперь наш узел IPv6 может составить сообщение NS или NA, когда потребуется. А в какой сетевой интерфейс он отправит это сообщение, если у него их несколько?
Что касается NS, то это сообщение уходит в интерфейс, подключенный к тому каналу, где проводится розыск соседа. Ведь мы с самого начала отметили, что отношение соседства между узлами определено только на заранее заданном канале. Поэтому розыск соседа проводится не глобально, а только после того, как узел-искатель определился с каналом. В свою очередь, соседа разыскивают, когда для него есть исходящий пакет и уже известен выходной интерфейс. Тогда будет вполне резонно передать и сообщение NS в тот интерфейс, на который указала маршрутизация исходящего пакета. Это обеспечит однозначный выбор канала для проведения розыска соседа, поскольку в архитектуре IP к интерфейсу нельзя одновременно подключить больше одного канала.
Что же касается сообщения NA, то в этом случае выбор интерфейса зависит от того, по запросу ли высылается данное сообщение. Если NA — это ответ на запрос NS (S = 1), то его достаточно передать в тот же сетевой интерфейс, откуда пришел запрос, и тогда оно заведомо попадет в нужный контекст канала. Ну, а если NA не по запросу (S = 0), то контекст канала известен из других соображений. Например, если это объявление о смене канального адреса, то его надо передать именно в тот интерфейс, на котором эта смена произошла.
Вот теперь розыск соседей на самом деле возможен: у нас есть форматы сообщений ND и правила работы с ними. А какую еще информацию извлекает из успешного обмена сообщениями NS и NA локальный узел А, кроме канального адреса искомого соседа Б? Очевидно, он узнаёт, что передача данных по каналу от А к Б и обратно, от Б к А, была возможна хотя бы на момент розыска соседа Б. Таким образом, ND позволяет узлу А убедиться в том, что доступность соседа Б симметричная, а NUD поддерживает актуальность этих сведений. В случае же асимметричной доступности, когда канал однонаправленный, ND просигнализирует полную недоступность узла Б. Иными словами, ND поддерживает только двунаправленные каналы [§3.2 RFC 4861].
Возможно, когда-нибудь в будущем состоится попытка распространить ND и на асимметричные каналы [§3.2 RFC 4861]. Произойдет это, конечно же, когда возникнет спрос на такие каналы в среде IPv6.
На этом мы завершили работу над первой частью протокола розыска соседей, посвященной разрешению индивидуальных адресов IPv6 в канальные адреса. Ниже мы встретим еще несколько задач, решение которых будет удобным и логически обоснованным, если его оформить в рамках того же самого протокола. Поэтому протокол розыска соседей IPv6 (Neighbor Discovery Protocol, NDP) не ограничен одним только разрешением адресов [§3 RFC 4861]. Но, как говорится, всему свое время.
Завершим раздел мы тем, что обратим внимание, как полезные разработки IPv6 постепенно находят себе дорогу обратно в
IPv4. Так, в современных версиях
Если птица ходит уткой, плавает уткой и крякает уткой, то ее называют уткой. (Дж. У. Райли)
CROWD: A witch! A witch! A witch! We've got a witch! A witch!
VILLAGER #1: We have found a witch, might we burn her?
CROWD: Burn her! Burn!
BEDEMIR: How do you know she is a witch?
VILLAGER #2: She looks like one.
BEDEMIR: Bring her forward.
WITCH: I'm not a witch. I'm not a witch.
BEDEMIR: But you are dressed as one.
WITCH: They dressed me up like this.
(Monty Python and the Holy Grail)
Вертит, как цыган солнцем. (Украинская поговорка)
До сих пор мы говорили об узлах IPv6 в целом, практически не проводя границ между хостом и маршрутизатором, и это было вполне оправданно, пока мы обсуждали базовые механизмы IPv6, не зависящие от типа узла.
Нам не повредит напомнить себе лишний раз определения хоста и маршрутизатора IP. В идеале, хост только создает и потребляет пакеты, тогда как маршрутизатор занят исключительно тем, что продвигает пакеты хостов. Конечно, на практике маршрутизатор обычно содержит в себе элементы хоста , необходимые для обработки служебных сообщений, удаленного управления и участия в протоколах маршрутизации.
Однако рано или поздно перед нами должен встать вопрос, можем ли мы положиться на наш опыт IPv4 и по-прежнему считать различия в устройстве и поведении хоста и маршрутизатора незначительными во всем, что не касается функции продвижения транзитного трафика.
Ведь, как мы знаем, в IPv4 произошла конвергенция этих двух типов узла, в результате чего между ними не осталось принципиальных отличий, а выбор роли узла свелся к настройке флажка, пропускать ли транзитный трафик. Современные операционные системы готовы обеспечить работу и хоста, и маршрутизатора IPv4, используя один и тот же программный код, а за этим стоит единство алгоритмов и структур сетевого стека. В частности, оба типа узлов IPv4:
пользуются таблицей маршрутов одного и того же вида: "префикс $$\to$$ следующий шаг" [ср. §3.3.1.2 RFC 1122 и §5.2.4.3 RFC 1812];
Хотя [§3.3.1.2 RFC 1122] говорил, что полноценная таблица маршрутов — необязательный элемент в сетевом стеке хоста IPv4, на практике она встречается повсеместно.
Мы сознательно не упоминаем ненумерованные интерфейсы "точка-точка", так как они не требуют розыска соседей.
А на самом глубоком уровне такую конвергенцию обеспечивает определенная модель взаимодействия хостов и маршрутизаторов одной подсети. Согласно этой модели, хост сам принимает решение, кому из соседей передать исходящий пакет, тогда как маршрутизатор только продвигает транзитный трафик, который был передан ему хостом. Чтобы эта модель работала, хост должен обладать полной информацией о логической топологии канала, которому назначена подсеть. Только тогда хост сможет правильно принять решение о том, является ли удаленный узел соседом. Это очень важное решение; ведь если хост ошибется и посчитает соседом узел, который на самом деле доступен только через маршрутизатор, то попытка передать ему пакет будет обречена на неудачу.
Сейчас мы снова опираемся на постулат, который утверждает, что одна подсеть отвечает не более чем одному каналу [§2.1 RFC 4291].
Проанализируем с этой точки зрения тест на соседство IPv4. Допустим, что узел А собирается передать пакет, адресованный узлу Б, посредством одного из своих интерфейсов И, которому назначены адреса 192.0.2.3/28 и 203.0.113.131/28. Следовательно, с точки зрения узла А, каналу , к которому подключен интерфейс И, назначены подсети 192.0.2.0/28 и 203.0.113.128/28. Если адрес назначения пакета Б равен 192.0.2.8 или, скажем, 203.0.113.136, то узел А немедленно определит, что этот адрес принадлежит соседу, потому что он содержит префикс непосредственно подключенной подсети. С точки зрения IP, такой пакет можно передать напрямую его адресату, полагаясь на канальный уровень стека. В то же время, ни один из адресов 192.0.2.150, 198.51.100.1 или 203.0.113.2 не принадлежит соседу, и, следовательно, такие адресаты доступны только косвенно, через маршрутизатор.
На самом деле, тому же каналу могут быть назначены и другие подсети IPv4, о которых узел А не знает, потому что из них ему адрес не назначили.
Эта логика IPv4 основана на гипотезе, что все узлы одной подсети — попарно соседи и могут передавать друг другу пакеты напрямую, не задействуя маршрутизатор. Такое допущение, безусловно, справедливо для широковещательных каналов, например, Ethernet, равно как и для соединений "точка-точка". Но насколько оно универсально? Существуют ли каналы, в которых оно не выполняется?
Оказывается, что в действительности спрос на такие каналы есть. Их принято обозначать термином "каналы множественного доступа без широковещания" или аббревиатурой NBMA (Non-Broadcast Multiple Access). Это не совсем точный термин, потому что на практике в каналах NBMA обычно отсутствует не только широковещание, но и свободная адресация одних узлов другими. Простейший пример такого канала — это топология "звезда" (рис. 5.15), где концевые узлы могут передавать пакеты центральному узлу (концентратору), тогда как напрямую обмениваться данными друг с другом концевые узлы не могут. В топологии "звезда" концентратор — сосед любому концевому узлу, и наоборот, но никакие концевые узлы — не соседи друг другу.
(рис 5.15) Канал NBMA "звезда" и его граф
Классические примеры каналов NBMA — это "звезды" на основе Frame Relay и ATM, где каждый "луч" был виртуальным соединением (virtual circuit).
Современными примерами каналов NBMA типа "звезда" служат кабельные, сотовые и xDSL-сети. В отличие от Ethernet и WiFi, в них концентратор не играет роли коммутатора. Поэтому обмен пакетами между конечными узлами в них возможен только на сетевом уровне, при поддержке маршрутизатора, логика которого часто встроена в сам концентратор.
Но даже локальную сеть Ethernet можно превратить в "звезду" NBMA с помощью частных VLAN. Делают это из соображений безопасности, чтобы централизованно управлять доступом между конечными узлами, не отказываясь при этом от простой схемы "одна ЛВС и одна подсеть".
Если бы мы проводили теоретический анализ топологии канала, то нам, возможно, следовало бы различать схемы "общая среда без широковещания" (когда возможна индивидуальная передача любому узлу, если его канальный адрес заранее известен) и "точка-многоточка" (звезда) в чистом виде [§4.1 RFC 4903]. Однако на практике чаще всего встречается та или иная их комбинация, когда только некоторые пары узлов — соседи между собой. Поэтому для нас сейчас эти две схемы — только частные, крайние случаи более общей модели, в которой возможность узлов канала адресовать друг друга индивидуально и группами ограничена заданным образом. Такую модель вполне допустимо причислить к NBMA.
Конечно, внимательный читатель обоснованно возразит, что такая "звезда" — это вовсе не один канал, а комбинация нескольких каналов "точка-точка", и он будет абсолютно прав, по крайней мере, в теории. Однако, с практической точки зрения, превратить каждый "луч" в отдельный канал — это неудачный выбор. Представьте себе концентратор, к которому уже подключены тысячи абонентов и ежедневно подключаются сотни новых. Если действовать прямолинейно и работать с каждым подключением как с отдельным каналом "точка-точка", то сегодня концентратору потребуется несколько тысяч сетевых интерфейсов, а завтра это число может достичь и миллиона. Хотя теоретик не увидит в этом ничего страшного, на практике число сетевых интерфейсов узла не может расти неограниченно.
Число сетевых интерфейсов узла ограничено не только потому, что программисты-разработчики сетевого стека поленились и использовали неоптимальный, немасштабируемый алгоритм работы с ними. Более фундаментальная проблема в том, что сетевой интерфейс — это центральный объект управления, с которым связана определенная конфигурация практически в каждом компоненте сетевого стека. Впрочем, этот аргумент можно отклонить на том основании, что объем ОЗУ в современных вычислительных системах практически неограничен, а любое число однотипных интерфейсов можно настроить с помощью одного шаблона. Еще один веский аргумент за ограничение числа сетевых интерфейсов заключается в том, что политику сетевой безопасности удобно формулировать в терминах входного и выходного интерфейсов пакета, но тогда число правил такой политики может расти как квадрат числа интерфейсов. Но даже эту трудность можно обойти, объединив интерфейсы в зоны безопасности или перейдя к иной модели управления доступом. Однако в конечном итоге оказывается, что проще ограничить число интерфейсов, нежели постоянно преодолевать препятствия.
Именно поэтому возникает спрос на искусственное решение, когда одна подсеть назначена множеству подключений, а это автоматически превращает их в один канал согласно фундаментальному правилу IP: "Одна подсеть — это один канал" [§2.1 RFC 4291]. Конечно, такая конструкция напоминает цирковой фокус с легким привкусом обмана. Тем не менее, давайте посмотрим, чего будет стоить ее реализация в IPv6. Ведь мы находимся в поле эксперимента, а искомая возможность вполне востребована. Как обычно, в процессе мы встретим и решим более глубокие и общие задачи.
Новые условия таковы, что канал может обладать нетривиальной логической топологией, когда не все его узлы — соседи. Ранее, когда модель канала была простой и требовала соседства между всеми узлами, эта топология канала была скрыта в самом тесте на соседство IPv4. Теперь же информацию о топологии канала придется задавать и хранить явным образом, например, в виде матрицы соседства, где 1 означает пару соседей, а 0 — разъединенную пару узлов. Такие матрицы для канала "звезда" (рис. 5.15) и широковещательного канала с тем же набором узлов приведены в табл. 5.1 и табл. 5.2, соответственно. Придется ли вводить подобную матрицу в настройки каждого из узлов канала?
Интересующие нас матрицы соседства — симметрические, потому что, согласно (нашей модели из §5.1, отношение соседства коммутативно.
| A | Б | В | Г | М | |
|---|---|---|---|---|---|
| А | 1 | 0 | 0 | 0 | 1 |
| Б | 0 | 1 | 0 | 0 | 1 |
| В | 0 | 0 | 1 | 0 | 1 |
| Г | 0 | 0 | 0 | 1 | 1 |
| М | 1 | 1 | 1 | 1 | 1 |
| А | Б | В | Г | М | |
|---|---|---|---|---|---|
| А | 1 | 1 | 1 | 1 | 1 |
| Б | 1 | 1 | 1 | 1 | 1 |
| В | 1 | 1 | 1 | 1 | 1 |
| Г | 1 | 1 | 1 | 1 | 1 |
| М | 1 | 1 | 1 | 1 | 1 |
Для начала мы ограничимся каналом, который обслуживает один маршрутизатор. Остальные узлы такого канала — хосты. Если бы топология этого канала была совершенно произвольной, то, наверное, обойтись без дублирования топологической информации было бы нельзя, и понадобился бы какой-то протокол ее распространения.
Однако для IP практическую ценность представляют только топологии, в которых маршрутизатор — сосед каждого хоста. Действительно, в противном случае для беспрепятственной работы канала потребовался бы транзитный трафик через хосты, а это по определению невозможно. Говоря языком элементарной теории графов, граф "звезда" с маршрутизатором в центре будет подграфом любой топологии канала, представляющей интерес в контексте TCP/IP. Это правило иллюстрирует рис 5.16(рис 5.16) Канал NBMA "бабочка" содержит подграф "звезда"
Тогда маршрутизатор IPv6 будет естественным хранителем информации о топологии канала, потому что он может обслуживать практически неограниченное число хостов (до $$\sim 2^{63}$$) и при этом он заведомо способен передать сведения о топологии канала каждому подключенному хосту. Механизм распространения этих сведений нам сейчас предстоит разработать.
Каких начальных настроек будет достаточно хосту IPv6 с одним сетевым интерфейсом, чтобы начать работу с сетью в таких условиях? Нетрудно убедится, что абсолютный минимум — это его собственный адрес и адрес маршрутизатора. В то же время, длина префикса подсети здесь излишня, так как она больше не содержит информации о топологии канала.
Здесь мы имеем в виду адрес хоста, область которого больше, чем внутриканальная. Помимо него, у хоста будет как минимум один внутриканальный адрес — к этому требованию мы уже пришли в §2.5.
В то же время, адрес маршрутизатора вполне может быть внутриканальным. Мы обсудили это там же, в §2.5.
Конечно же, как это было сказано в §2.6, адрес IPv6 назначают не всему узлу в целом, а определенному его интерфейсу — в данном случае, интерфейсу в интересующий нас канал. По текущим условиям, такой интерфейс у каждого узла один, и это позволяет нам для простоты говорит "адрес узла".
В отсутствие других сведений хосту IPv6 не остается ничего, кроме как передавать (весь исходящий трафик через этот маршрутизатор, так что для хоста это будет (маршрутизатор по умолчанию (default router).
Очевидное исключение — это пакеты, в которых хост адресует сам себя. Их не следует отправлять на маршрутизатор по умолчанию, потому что такие пакеты вообще не покидают границ хоста.
Любой имеющий опыт работы с IPv4 сказал бы, что у нашего модельного хоста есть только маршрут по умолчанию, (::/0, и нет маршрута на непосредственно подключенную подсеть. Однако суть как раз в том, что модель хоста IPv6 целенаправленно избегает появления таблицы маршрутов среди структур данных хоста. Сделано это, не в последнюю очередь, для упрощения минимальной реализации, которая найдет себе место во встроенных системах. Фактически, происходит попытка снять с хоста все функции, которые будут заведомо доступны в маршрутизаторе. Реализация хоста IPv6 может включать в себя эти функции факультативно, но они не обязательны, и их отсутствие не повредит работоспособности хоста.
Чтобы начать передачу данных через маршрутизатор, хосту надо выяснить его канальный адрес, а для этого, в зависимости от типа канала, может потребоваться розыск соседа. Однако успех этой операции обеспечен, несмотря на сложную топологию канала, тем, что маршрутизатор — заведомо сосед хоста, если канал отвечает базовым требованиям TCP/IP. Так оказывается, что для начала работы хосту IPv6 вообще не нужны дополнительные сведения о топологии канала: ему достаточно слать весь исходящий трафик через маршрутизатор по умолчанию, и он уже способен это сделать.
(рис 5.17) Маршрутизация обратно в канал NBMA и за его пределы
Для топологии "звезда" этого решения будет вполне достаточно, так как у хостов все равно нет иного способа обмениваться данными, кроме как через маршрутизатор, хотя формально они подключены к одному каналу. Такая конфигурация представлена на рис. 5.17.
Теперь усложним топологию канала и допустим, что она включает в себя прямые связи между некоторыми хостами. Для примера рассмотрим топологию "бабочка" (рис. 5.16), где две пары хостов снабжены такими связями. Ее матрица соседства приведена в 5.3. Как мы уже отметили, "звезда" обязательно будет подграфом такой топологии (см. рис. 5.16), и наша простейшая схема работы хоста, с одной стороны, останется возможной. С другой стороны, она утратит свою оптимальность, так как хосты А и Б или В и Г смогли бы обмениваться пакетами напрямую, не создавая нагрузки на маршрутизатор М. Сведения о соседстве хостов А и Б, а также В и Г, хранятся маршрутизатором М. Каким образом М мог бы сообщить, скажем, хосту А, что тот вполне способен передавать пакеты хосту Б напрямую? И когда М следует это сделать? Потребуется ли особый протокол, по которому А запросит недостающую информацию у М?
| А | Б | В | Г | М | |
|---|---|---|---|---|---|
| А | - | 1 | 0 | 0 | 1 |
| Б | 1 | - | 0 | 0 | 1 |
| В | 0 | 0 | - | 1 | 1 |
| Г | 0 | 0 | 1 | - | 1 |
| М | 1 | 1 | 1 | 1 | - |
На самом деле, здесь достаточно простой (переадресовки (redirect), которую будет естественно оформить в виде сообщения ICMPv6, принадлежащего к семейству ND [§4.5 RFC 4861]. Последовательность событий может быть такой:
затем, чтобы оптимизировать будущий трафик, М направляет хосту А сообщение ND типа "переадресовка", которое уведомляет, что адресат Б доступен через следующий шаг Б, то есть напрямую;
хост А принимает переадресовку к сведению, исправляя запись Б в кэше адресатов на "Б $$\to$$Б";
Конечно, маршрутизатор М не должен слать переадресовку на (каждый пакет от А к Б, если хост А игнорирует переадресовки и продолжает слать пакеты через М. Из соображений устойчивости и безопасности частоту переадресовок следует ограничивать [§8.2 RFC 4861]. Впрочем, это справедливо практически для всех сообщений ICMPv6.
У этого решения есть несколько очевидных преимуществ. Во-первых, оно использует уже готовые структуры данных, в частности, кэш адресатов хоста. Во-вторых, оно требует всего лишь одного нового сообщения ND с простыми свойствами. В-третьих, оно устойчиво к потере и дублированию переадресовок. Наконец, в-четвертых, матрица соседства, хранимая маршрутизатором М, может изменяться по ходу работы канала, и хост А оперативно обновит свой взгляд на топологию канала без внешнего вмешательства благодаря работе кэша адресатов. Например, если связь А–Б исчезнет, то хост А станет слать трафик в адрес Б через маршрутизатор М, как только устареет прежняя запись в кэше адресатов DC.
Своевременно обнаружить исчезновение связи А–Б и переключиться обратно на маршрутизатор М хосту А помогут механизм (NUD (§5.1) и обратная связь между записями NC и DC. Так, когда NUD сигнализирует недоступность соседа Б, можно удалить заодно и все записи DC, чей следующий шаг равен Б. Тогда время жизни самих записей DC можно и не ограничивать. Мы обобщим эту идею ближе к концу раздела.
Данный механизм настолько прост и универсален, что он заслуживает роли стандартного для всех хостов IPv6. Кроме того, он предъявляет весьма низкие требования к вычислительным возможностям хоста, а это как нельзя лучше соответствует тенденции применять IP в промышленных сетях управления, где хосты — устройства маломощные и с низким энергопотреблением.
Таким образом, хост IPv6 при передаче исходящих пакетов руководствуется существенно иными правилами, чем хост IPv4. Это различие нам следует четко отразить в терминологии и процедурах, чтобы избежать путаницы.
Независимо от версии IP, перед хостом стоит одна и та же основная задача:
(Дано: индивидуальный адрес назначения исходящего пакета IP.
(Вопрос: доступен ли сетевой интерфейс с этим адресом непосредственно, на канальном уровне?
Что означает для хоста H доступность удаленного сетевого интерфейса I на канальном уровне? Очевидно, это значит, что хост H посредством одного из его собственных, локальных интерфейсов J может обмениваться данными канального уровня с интерфейсом I, а значит, и его узлом-владельцем. Ведь всякий сетевой интерфейс а) подключен ровно к одному каналу и б) принадлежит ровно одному узлу. (Когда интерфейс не подключен, он не готов к работе и, с нашей точки зрения, не существует.) Необходимое, но недостаточное условие доступности на канальном уровне — это чтобы у хоста H нашелся интерфейс J, подключенный к тому же каналу L, что и интерфейс I. Дополнительное условие — это фактическая возможность обмена данными на канальном уровне между интерфейсами I и J по каналу L. Если, например, в локальных сетях второе следует из первого, то в каналах NBMA все зависит от логической топологии данного канала.
Будет любопытно отметить, что понятие "петли" (loopback) позволяет обобщить эту конструкцию и на случай, когда интерфейс назначения I принадлежит локальному хосту H. В этом случае тривиальное решение выглядит как J = I. Впрочем, если у хоста несколько интерфейсов, подключенных к каналу L, то возможны и нетривиальные решения, где J $$\ne$$ I.
Если ответ на этот ключевой вопрос утвердительный, то хост проведет разрешение сетевого адреса назначения в отвечающий ему канальный адрес с помощью ARP или ND, в зависимости от версии IP, и сможет передать пакет его адресату напрямую, из интерфейса в интерфейс. В противном случае хост должен воспользоваться услугами маршрутизатора.
А вот общепринятые способы найти ответ на данный вопрос в двух версиях IP существенно разнятся.
Как мы уже сказали, хост IPv4 исходил из предположения, что все адреса в одной с ним подсети доступны напрямую. О таких адресах мы говорили, что они непосредственно подключенные (directly connected). У непосредственно подключенного адреса может быть всего два состояния: или он вообще не назначен, или он принадлежит интерфейсу, который доступен локальному хосту на канальном уровне. Все прочие адреса не являются непосредственно подключенными и доступны хосту только через маршрутизатор. Классификацию адресов хост IPv4 проводил с помощью несложной побитовой арифметики, используя список префиксов подсетей, назначенных его собственным интерфейсам. Вдобавок, если результат теста был положительный, то наиболее точный префикс однозначно указывал на интерфейс, сквозь который доступен непосредственно подключенный адрес.
Напротив, хост IPv6 больше не доверяет информации о подсетях, потому что она не всегда отражает действительную топологию каналов. Хост IPv6 не может априори определить, принадлежит ли произвольный адрес соседу, и потому рассчитывает на дополнительные источники информации, чтобы классифицировать каждый адрес назначения индивидуально. (Если об адресе нет иной информации, то хост IPv6 по умолчанию полагает, что соответствующий сетевой интерфейс недоступен на канальном уровне, а адрес находится "(вне канала" (off-link) [§3 RFC 5942].
Обратите внимание на это правило. Оно существенно отличается от общепринятой практики IPv4.
Из каких источников хост может почерпнуть иные сведения? Во-первых, адреса его собственных интерфейсов доступны ему напрямую, потому что хост всегда может послать пакет сам себе, минуя маршрутизатор. Во-вторых, если в настройках хоста указан адрес маршрутизатора по умолчанию, то он также заведомо доступен напрямую. В свою очередь, эта настройка открывает хосту возможность обмена данными с другими хостами, даже если их адреса "вне канала". В процессе этого обмена хост может получить от маршрутизатора переадресовку вида "Б$$\to$$Б" и таким образом узнать, что интерфейс с адресом Б доступен ему на канальном уровне. Все эти источники информации сообщают хосту IPv6, что адрес находится "(на канале" (on-link).
Модель, в которой все адреса по умолчанию находятся "вне канала", довольно нова и опыт работы с ней относительно невелик, так что даже некоторые стандарты потребовали корректив. Скажем, все еще действующий стандарт ND предписывал, что адрес — "на канале", если о нем пришло объявление NA, или же если он выступает источником любого сообщения ND [§2.1 RFC 4861]. Однако это поведение противоречит модели, в которой единственный достоверный источник такой информации — маршрутизатор. Кроме того, оно открывает возможность для атаки, когда злоумышленник на канале перехватывает трафик, предназначенный узлу вне канала. Поэтому [п.3 §3 RFC 5942] запрещает хосту подобный трюк.
В то же время, хосту не возбраняется "разогревать" свой кэш соседей NC, заранее создавая в нем записи, но не делая при этом выводов о непосредственной доступности узлов. К примеру, получив запрос NS с опцией SLLA, хост создает о его источнике запись NC в состоянии (ПРОСРОЧЕННАЯ [§3 RFC 5942 и §7.2.3 RFC 4861], потому что в будущем это позволит (сэкономить время и обойтись без явного розыска соседа, как мы обсудили в §5.1. Но воспользоваться этой записью NC для прямой передачи хост будет вправе, только когда маршрутизатор подтвердит, что адресат "на канале", и будет создана соответствующая запись в кэше адресатов DC.
Только теперь, поняв и сформулировав главное различие между алгоритмами теста на соседство IPv4 и IPv6, мы можем безопасно оптимизировать алгоритм IPv6, не рискуя навредить нашему пониманию основ.
На практике о (некоторых диапазонах адресов может быть известно, что они "на канале", с точки зрения данного хоста. К их числу относятся:
Кроме того, условно считается, что групповые адреса всегда "на канале", поскольку групповой трафик IP распространяется в сети не так, как индивидуальный. Групповые маршрутизаторы слушают все обслуживаемые ими группы и потому, с точки зрения источника трафика, они ничем не отличаются от рядовых членов группы на том же канале. Подробнее мы поговорим об этом в §6.4. Кроме того, к групповым адресам никогда не применяют ND. Поэтому групповые адреса стоят особняком, и нет смысла пытаться включить их в наш текущий дискурс о критериях "на канале/вне канала".
В первых двух случаях диапазоны адресов уже представлены префиксами. Третий случай не столь важен, но в нем диапазон тоже можно представить набором префиксов. Поэтому вместо произвольных диапазонов мы можем говорить о префиксах "на канале". Если список этих префиксов сообщить хосту, то он сможет эффективнее использовать свое соседство с другими узлами и реже обращаться к услугам маршрутизатора по умолчанию. Соответствующая структура в памяти хоста IPv6 так и называется: (список префиксов (Prefix List). Если адрес назначения содержит в себе один из этих префиксов, то он для данного хоста — "на канале".
Сделаем замечание по терминологии. Строго говоря, префикс может (содержаться в адресе, тогда как адрес может (принадлежать блоку, представленному этим префиксом. Поэтому говорить, что "адрес принадлежит префиксу", как советовал нам редактор, было бы искажением технической сути этих отношений.
Свежеполученный опыт подсказывает нам, что список префиксов надо заполнять с осторожностью, чтобы не навредить работе каналов нетривиальной топологии. По умолчанию в этом списке ровно один элемент — внутриканальный префикс FE80::/10 [§5.1 RFC 4861, §3 RFC 5942].
По этой причине хосты на канале NBMA не могут свободно взаимодействовать друг с другом, используя внутриканальные адреса. На это ограничение приходится идти ввиду особой роли внутриканальных адресов. Хотя, зонная архитектура IPv6 вполне допускает маршрутизацию внутриканальных адресов маршрутизатором обратно в тот же канал, использовать эту возможность по умолчанию не следует.
В то же время, скажем, интерфейс, подключенный к каналу Ethernet, еще ничего не говорит о фактической топологии этого канала. Например, с помощью частных VLAN топологию такого канала можно ограничить и превратить его из широковещательного в NBMA. По этой причине хосту не следует автоматически вносить в список префиксов подсети только потому, что они назначены интерфейсам определенного типа.
Согласно нашему генеральному плану, точными сведениями о топологии канала обладает маршрутизатор. Поэтому самая достоверная информация о списке префиксов могла бы исходить именно от маршрутизатора. Пока что мы не располагаем подходящим механизмом для этого, но скоро он у нас тоже появится (в §5.4.2).
С другой стороны, теперь подсеть, в которой находится интерфейс хоста IPv6, больше не влияет на классификацию "на канале" или "вне канала", как это было в IPv4. Поэтому мы вполне можем допустить случай, когда удаленный адрес оказывается "на канале", хотя не принадлежит подсети хоста. Для примера рассмотрим сценарий на рис 5.18(рис 5.18) Один канал и один маршрутизатор, но две подсети
Как работала бы подобная схема в IPv4? Допустим, у хоста А был бы адрес (192.0.2.33/24, у хоста Б - (203.0.113.44/24, а у маршрутизатора М — (192.0.2.1/24 и (203.0.113.1/24. В этой схеме весь трафик между хостами А и Б неизбежно проходил бы через маршрутизатор М. Почему? Во-первых, если бы хосту А пришла переадресовка ICMP вида "(203.0.113.44 $$\to$$ (203.0.113.44", то хост А проигнорировал бы ее, потому что следующий шаг в ней не являлся непосредственно подключенным [§3.2.2.2 RFC 1122]: (203.0.113.44 не принадлежал подсети (192.0.2.0/24. А во-вторых, маршрутизатор М не стал бы слать хосту А такую переадресовку [§5.2.7.2 RFC 1812], потому что он заранее знал: она будет проигнорирована. Те же соображения, с точностью до перестановки адресов, определили бы и обратный ход трафика от Б к А.
Теперь же, в IPv6, у хоста А не будет причин не принять переадресовку "Б $$\to$$Б" просто потому, что именно маршрутизатор —
достоверный источник сведений о топологии канала [§8.1 RFC 4861]. В свою очередь, маршрутизатор М не должен ограничивать переадресовки одной
подсетью, если он располагает информацией о том, что хосты А и Б — соседи. И тогда хост А сможет узнать и принять к сведению, что хост Б "на
канале" и доступен напрямую, как это показано на рис 5.19(рис 5.19) Механизм переадресовки
Обратите особое внимание на это важное различие между переадресовками IPv4 и IPv6 [§3.1 RFC 4861].
Это новое свойство нам следует перенести и на список префиксов. Для этого скажем: элементы списка префиксов не обязаны принадлежать подсетям, в которых находится сетевой интерфейс хоста. К примеру, список префиксов хоста Х с адресом (2001:DB8:33::33/64 вполне может содержать префиксы (2001:DB8:33::/48 (префикс короче подсети) и (2001:DB8:777::/64 (другая подсеть). Все адреса с такими префиксами будут для хоста Х "на канале", и тот сможет слать пакеты их владельцам напрямую.
Теперь нам пора поработать над форматом переадресовки. Как мы уже сказали, она будет разновидностью сообщения ICMPv6 из семейства ND [§4.5 RFC 4861]. Переадресовка — это сообщение с любопытными свойствами. С одной стороны, переадресовка — это полезная подсказка хосту, а вовсе не извещение об ошибке. Поэтому ее численный тип находится в диапазоне справочных сообщений 128–255 и равен 137. С другой стороны, переадресовка высылается в ответ на произвольный пакет IPv6, что отличает ее от других справочных сообщений и роднит с извещениями об ошибках. В этом случае, как мы знаем, служебное сообщение должно содержать в себе начало пакета, который его вызвал, чтобы получатель смог более точно отреагировать на него, например, определив, к какому сеансу TCP относится это сообщение.
Разрешить это противоречие в свойствах переадресовки будет довольно просто, если мы воспользуемся механизмом опций ND и поместим пакет-виновник (или его начало) в специально отведенную для этой роли опцию "переадресованный заголовок" (Redirected Header), показанную на рис. 5.20 Правила работы с ней очевидны:
(рис 5.20) Опция ND "переадресованный заголовок"
А какие обязательные поля мы поместим в тело переадресовки? Назначение переадресовки заключается в том, чтобы сообщить получателю, что оптимальный путь к адресату Б пролегает через следующий шаг Ш, где Б и Ш — адреса IPv6. Поэтому фиксированная часть тела переадресовки состоит, помимо выравнивания, всего из двух адресов IPv6. Как показано на рис. 5.21 первым из них расположен адрес следующего шага Ш. Так как его владелец — заведомо сосед получателя, то, по терминологии ND, это адрес цели (target address). Непосредственно за ним идет адрес назначения Б.
(рис 5.21) Переадресовка IPv6
Адрес цели идет первым для единообразия с другими сообщениями ND.
Хотя адрес назначения также содержится в копии пакета-виновника (опция "переадресованный заголовок"), его дублирование в фиксированной части переадресовки облегчает работу ее получателя.
Здесь мы видим еще одно важное отличие между переадресовками IPv6 и IPv4. Как мы помним, переадресовка IPv4 могла быть для одного адреса или целой сети, хотя формат переадресовки для сети был классовым и не имел смысла в CIDR. Переадресовка же IPv6 — исключительно для одного адреса, и это позволяет хранить ее сведения в кэше DC.
Адреса Б и Ш вполне могут совпадать. Именно этот случай нам сейчас особенно интересен. Ведь такой переадресовкой маршрутизатор сообщает, что узел Б, он же Ш, находится "на канале".
Случай, когда адреса Б и Ш не совпадают, тоже, безусловно, важен на практике. Такая переадресовка говорит хосту, что оптимальный путь к адресату Б пролегает через другой маршрутизатор на том же канале, а именно Ш.
Прочие требования к годной переадресовке вполне предсказуемы [§8.1 и §8.2 RFC 4861]:
Эта проверка также защищает от переадресовок, которые задержались в сети. Например, если хост А был переадресован маршрутизатором М1 к маршрутизатору М2, а тем, с свою очередь, к маршрутизатору М3, то запоздавшая копия переадресовки М1 будет хостом А проигнорирована.
А как быть хосту, если от его текущего маршрутизатора по умолчанию пришла переадресовка вида "Б $$\to$$Ш", но в кэше DC еще нет записи об адресате Б? Хотя стандарт признает такую переадресовку законной [§8.1 RFC 4861] и рекомендует создать новую запись "Б$$\to$$Ш" [§8.3 RFC 4861], подобная переадресовка подозрительна, потому что она содержит сведения об адресате, которому хост в обозримом прошлом никаких пакетов не слал. Разобраться в этой ситуации хосту поможет копия пакета-виновника в теле переадресовки.
пошаговая адресация маршрутизаторов происходит по их внутриканальным адресам [§8 RFC 4861], и поэтому:
oв заголовке IPv6 адрес источника должен быть внутриканальным, так как переадресовка исходит от маршрутизатора данного канала;
oв теле переадресовки адрес цели Ш должен или совпадать с адресом назначения Б (переадресовка на хост), или быть внутриканальным (переадресовка на другой маршрутизатор).
Под пошаговой адресацией мы имели в виду обращение к узлу, которое происходит, когда другой сосед использует его в качестве следующего шага пакета. Архитектура IPv6 самым настоятельным образом содействует тому, чтобы хост в этом случае обращался к маршрутизатору по его внутриканальному адресу.
Та же тенденция прослеживается и во взаимодействии между маршрутизаторами IPv6. Так, отношения смежности между маршрутизаторами IS IS [§3 RFC 5308] и OSPF [§2.5 RFC 5340] устанавливаются именно по их внутриканальным адресам.
Пока хосты IPv6 обращаются к маршрутизаторам по внутриканальным адресам, хостам не придется делать исключения из правил "на канале" — "вне канала". Ведь внутриканальный префикс FE80::/10 автоматически попадает в список префиксов "на канале". В противном же случае хост должен был бы постоянно "помнить", что адрес маршрутизатора по умолчанию находится "на канале". Это несложно, но немного нарушает стройность алгоритма передачи пакетов хостом.
Полученный формат переадресовки уже позволяет сообщить сам факт, что адрес назначения — "на канале". Но всегда ли этого достаточно? Допустим, хост А послал пакет адресату Б через маршрутизатор М, получил от маршрутизатора переадресовку вида "Б $$\to$$Б" и сохранил это соответствие в своем кэше адресатов DC. Каковы его дальнейшие действия? Когда придет время послать следующий пакет в адрес Б, кэш адресатов DC укажет хосту А, что адрес Б — "на канале" и принадлежит соседу. То есть придет время для розыска соседа Б с помощью протокола ND. В свою очередь, протокол ND подразумевает, что сосед Б может получать групповые пакеты, рассылаемые хостом А.
Но что будет, если канал между А и Б — NBMA без группового вещания между хостами? Тогда на канальном уровне хост А мог бы передавать соседу Б индивидуальные кадры, если бы знал его канальный адрес. В то же время, групповой запрос NS не дойдет до Б. В этом случае розыск соседа закончится неудачей и обмен данными между А и Б окажется невозможным.
Чтобы обойти и эту особенность каналов NBMA, нам достаточно довести до логического завершения новую функцию маршрутизатора IPv6. Как мы уже отмечали, маршрутизатор IPv6 — это координатор передачи данных по каналу между хостами. Тогда именно он и должен сообщить хосту А недостающие сведения. Для этого достаточно дополнить переадресовку уже доступной нам опцией ND "канальный адрес цели" (§5.1) и поместить в нее канальный адрес узла Б.
Эта опция в переадресовке не помешает и при передаче по широковещательному каналу, так как хосту А не придется самостоятельно разыскивать соседа Б. Тем не менее, для широковещательных каналов это просто оптимизация, тогда как для каналов NBMA это важный механизм, который позволяет IPv6 работать с такими каналами без их искусственной адаптации к широковещательной семантике.
Что хост А сделает с информацией из этой опции? Очевидно, он поместит ее в свой кэш соседей NC. Так как информация исходит от маршрутизатора и еще не подтверждает обоюдной доступности А и Б, подходящим состоянием для записи NC будет ПРОСРОЧЕННАЯ. Этой записью уже можно воспользоваться для непосредственной передачи пакета. Когда хост Б откликнется на этот пакет, то по сигналу вышестоящего протокола запись NC станет ДОСТУПНОЙ. Если же вышестоящий протокол — строго однонаправленный, то хосту Б все равно придется ответить на индивидуальный запрос NS с опцией "канальный адрес источника" (§5.1), который пошлет хост А, когда запись NC перейдет по тайм-ауту в состояние ИСПЫТАНИЕ.
Обратите внимание: хост Б будет обязан пройти тут же самую процедуру переадресовки, чтобы получить право слать прикладные пакета напрямую хосту А, полагая того соседом "на канале".
Последний вопрос о работе этого механизма состоит в том, как маршрутизатор М узнает канальный адрес узла Б в отсутствие полноценного группового
вещания. Конечно, последнее средство, пригодное для всех разновидностей NBMA — это ручная настойка кэша соседей М. Однако М сможет найти
канальный адрес Б автоматически, если канал NBMA эмулирует групповое вещание по принципу веера, известное как "псевдошироковещание"
(pseudobroadcast
До сих пор мы сознательно ограничивались сценарием, когда канал обслуживает один маршрутизатор, а у каждого хоста один сетевой интерфейс, помимо "петли". Насколько трудно будет снять эти ограничения? Начнем с первого из них и рассмотрим канал с несколькими маршрутизаторами. Чтобы подключенный к тому же каналу хост смог пользоваться их услугами, ему, прежде всего, понадобится список их адресов вместо одного адреса маршрутизатора по умолчанию. Так возникает новая структура данных в памяти хоста: список маршрутизаторов по умолчанию (Default Router List). Сейчас мы допустим, что этот список указан вручную в начальных настройках хоста, а над его автоматическим заполнением мы поработаем чуть позже, в §5.4.2. Мы не станем усложнять нашу конструкцию системой приоритетов и положим, что все маршрутизаторы в списке эквивалентны.
Не имея оснований предпочесть какой-то конкретный маршрутизатор из списка, хост может начать работу, просто выбрав запись случайным образом. Этого уже будет достаточно, скажем, чтобы распределить нагрузку густозаселенного канала на несколько маршрутизаторов. Кроме того, дублирование маршрутизаторов повысит отказоустойчивость сети, если каждый хост сможет самостоятельно обнаружить, что его текущий маршрутизатор "пропал без вести", и выбрать из списка новый, "живой" маршрутизатор. Придется ли нам изобрести для этого новый протокол? К счастью, нет. Ведь в нашем распоряжении уже есть замечательный механизм NUD, для работы которого достаточно протокола ND — см. §5.1. Когда NUD сигнализирует о недоступности текущего маршрутизатора, хост может проверить работоспособность следующего по списку, и так далее. Совсем удалять недоступный маршрутизатор из списка, конечно же, не следует. Ведь вполне может оказаться, что к тому времени, когда последний маршрутизатор в списке засбоит, первый уже вернется в строй. На этом задача о нескольких маршрутизаторах решена.
Конечно, поиск "живого" маршрутизатора открыт для оптимизации. К примеру, хост может активно следить за всеми маршрутизаторами в списке с помощью все того же механизма NUD и ранжировать их по состоянию записи в кэше соседей NC: ДОСТУПНАЯ запись лучше ПРОСРОЧЕННОЙ, а та предпочтительнее НЕПОЛНОЙ.
Справедливости ради заметим, что §3.3.1.2 RFC 1122 требовал от хоста IPv4 обязательной поддержки нескольких маршрутизаторов по умолчанию. В то же время, §3.3.1.4 того же RFC 1122 признавал, что в IPv4 нет надежного и общепринятого способа узнать о недоступности текущего маршрутизатора. Этот конфликт между желаемым и действительным неизбежно сказался на практике IPv4, где список маршрутизаторов по умолчанию можно встретить редко.
Сможем ли мы так же легко и изящно снять второе ограничение и обеспечить эффективную работу хоста IPv6, подключенного к нескольким каналам? Для начала мы вновь обратим внимание на уже известный нам факт: отношение соседства определено только в пределах заданного канала. Иными словами, узел Б не может быть соседом узла А "вообще" — они будут соседями только на вполне определенном канале К. В свою очередь, окном в канал для хоста выступает сетевой интерфейс, а структуры данных хоста, которые управляют передачей пакетов в нашей модели, самым непосредственным образом работают с отношением соседства:
Поэтому все эти структуры привязаны к определенному сетевому интерфейсу и не имеют смысла для других интерфейсов того же хоста
К примеру, "продвинутый" хост с несколькими подключениями может участвовать в протоколе маршрутизации и самостоятельно вычислять оптимальные направления передачи пакетов вместо того чтобы надеяться на маршрутизаторы по умолчанию.
На первый взгляд, есть случаи, когда и наш эталонный хост может сам выбрать оптимальный выходной интерфейс. Например, вот типичное заблуждение: "Если адресат исходящего пакета — "на канале" на одном из интерфейсов, то надо выбрать именно этот интерфейс". Но как быть, если этот канал засбоил и в данный момент адресат доступен только через маршрутизатор другого канала? Дело в том, что множественное подключение применяют для решения довольно сложных задач, таких как отказоустойчивость высокой категории, и простые решения здесь не срабатывают. В то же время базовые механизмы хоста IPv6 должны оставаться простыми, чтобы они были по силам не только серверу, но и кофеварке.
Мы же предположим, что у эталонного хоста IPv6 в каждый момент времени есть один выходной интерфейс по умолчанию, который и используется для передачи пакетов.
Теперь нам пора свести воедино наши соображения о том, каков принципиальный алгоритм передачи пакета эталонным хостом IPv6 [§5.2 RFC 4861]. Вызов этого алгоритма будет происходить на каждый исходящий пакет. Входными данными алгоритма послужат сетевой адрес назначения пакета и требуемый выходной интерфейс, а вернуть он должен канальный адрес следующего шага, или же ошибку, если следующий шаг найти не удалось.
Первое решение хоста состоит в том, является ли адрес назначения индивидуальным или групповым. Определить это просто благодаря тому, что все групповые адреса имеют префикс FF00::/8. Дальнейшая работа над групповым адресом для нас сейчас интереса не представляет, так как, согласно нашему плану, групповые адреса IPv6 всегда "на канале", а канальный групповой адрес просто вычисляется из сетевого группового адреса по правилам инкапсуляции IPv6 для каналов данного типа. Например, в Ethernet это уже знакомая нам элементарная операция: взять 32 младших бита группового адреса IPv6 и присоединить к ним префикс 33 33. Поэтому сосредоточим наше внимание на индивидуальных адресах назначения.
Далее хосту надо найти сетевой адрес следующего шага. Здесь хост пытается сэкономить время, применяя кэш адресатов DC, привязанный к выходному интерфейсу. Если адрес назначения уже содержится в левой части записи DC, то правая часть этой записи сразу же отвечает на два вопроса: явным образом она сообщает сетевой адрес следующего шага, а неявно говорит, "на канале" ли адресат. Впрочем, хосту уже неважен ответ на второй вопрос, потому что он знает ответ на первый.
Если же записи об адресе назначения в кэше DC нет, хост должен определить, "на канале" ли адресат. Здесь безопаснее ошибочный диагноз: "Вне канала", — потому что в этом случае пакет уйдет через маршрутизатор. Напротив, ошибочный диагноз: "На канале", — приведет к невозможности передать пакет. Поэтому хост сопоставляет адрес назначения со списком префиксов выходного интерфейса и делает вывод: "На канале", — только если адрес назначения содержит хотя бы один из этих префиксов. В противном же случае хост обязан действовать так, как если бы адресат был "вне канала".
Действующий стандарт ND предписывает поиск длиннейшего префикса [§5.2 RFC 4861], хотя эта информация здесь явно избыточна. Ведь даже если список префиксов содержит более короткий префикс наряду с содержащим его префиксом большей длины, результат проверки не зависит от того, какой именно префикс был обнаружен в адресе назначения. Допустим, например, что список префиксов состоит всего из двух пунктов: 2001:DB8::/48 и 2001:DB8::/64. Тогда адрес 2001:DB8::1234 будет "на канале" вне зависимости от того, какой из префиксов "сработал" при проверке. Информация о длиннейшем префиксе могла бы понадобиться, если бы надо было распространить поиск на несколько сетевых интерфейсов и выбрать интерфейс с самым точным префиксом. Однако выбор выходного сетевого интерфейса сознательно не включен в современную версию ND. По-видимому, поиск длиннейшего префикса в стандарте ND остался с тех пор, когда поддержка нескольких интерфейсов на уровне ND была предметом исследования. Кроме того, поиск самого точного префикса может быть нужен, если хост ведет статистику для каждого префикса на канале: счетчик числа обращений и т.п.
Если по итогам этой проверки адрес назначения находится "на канале", то сетевой адрес следующего шага совпадает с адресом назначения. В противном случае хост выбирает кандидата из списка маршрутизаторов по умолчанию, например, руководствуясь сведениями NUD. Окончательный результат помещается в кэш DC.
Если сведений NUD ни об одном маршрутизаторе нет, то достаточно перебирать список маршрутизаторов по кругу [§6.3.6 RFC 4861]. Тогда довольно скоро в кэше NC появится актуальная информация о доступности хотя бы некоторых маршрутизаторов.
В этом и состоит первая часть эталонного алгоритма передачи пакета хостом IPv6: выбор следующего шага (маршрутизация). Давайте представим его в виде блок-схемы рис 5.22(рис 5.22) Алгоритм маршрутизации выходного пакета эталонным хостом IPv6
Теперь сетевой адрес следующего шага выбран, и слово за модулем ND, чьей задачей будет выяснить отвечающий ему канальный адрес. Процесс ND тоже старается сэкономить время и силы, на этот раз с помощью кэша соседей NC при выходном интерфейсе. Если данный адрес уже закэширован и запись о нем полная, то есть содержит канальный адрес, то достаточно вернуть последний. Конечно, сам факт обращения к записи NC может включить фоновый механизм ее освежения согласно NUD, например, если запись ПРОСРОЧЕННАЯ. Тем временем алгоритм передачи IPv6 успешно завершает работу, так как любой полной записью NC можно немедленно пользоваться для передачи пакета.
И, наконец, если полной записи о следующем шаге в кэше NC еще нет, то хосту остается только поместить пакет в буфер и начать процедуру розыска соседа. В результате ее работы возникнет полная запись NC, если искомый узел доступен, конечно. Так задача сведется к предыдущей, когда запись NC уже есть.
Какими любопытными свойствами обладает этот алгоритм? С одной стороны, он вполне способен распределить нагрузку на несколько маршрутизаторов [RFC 4311], но при этом он не страдает от недостатков случайного распределения, потому что кэширует следующий шаг для каждого адресата.
Проблемы, которые влечет за собой бессистемное распределение пакетов по разным путям, мы уже упомянули в §5.1.
С другой стороны, этот алгоритм не обременен мгновенной проверкой доступности следующего шага. Допустим, хост до некоторых пор пользовался одним маршрутизатором, а затем его маршрутизатор-фаворит неожиданно взяли и выключили. Тогда алгоритм передачи хоста продолжит возвращать канальный адрес выключенного маршрутизатора до тех пор, пока фоновый механизм NUD не сигнализирует о его недоступности и пока не исчезнут указывающие на него записи кэша DC. И только после этого произойдет полное переключение на новый маршрутизатор.
Конечно, это компромисс между надежностью и производительностью: маршрутизаторы не исчезают ежесекундно, тогда как проверять доступность маршрутизатора перед каждым пакетом было бы накладно. К тому же, благодаря непрерывной обратной связи с вышестоящими протоколами, механизм NUD может установить свои тайм-ауты весьма короткими и обнаружить пропажу соседа в считанные секунды после того, как прекратятся сигналы вышестоящих протоколов о его доступности.
Дальнейшая оптимизация состоит в том, чтобы удалить все записи кэша DC, которые ссылаются на пропавшего соседа, как только механизм NUD обнаружил его недоступность. Это вызовет пересмотр следующего шага для всех активных адресатов, ранее доступных через пропавшего соседа.
Кроме того, если пропавший сосед — маршрутизатор по умолчанию, то надо на какое-то время исключить его из кандидатов для алгоритма передачи. Например, при циклическом обходе списка маршрутизаторов достаточно перейти к следующему элементу в нем. Тогда пропавший маршрутизатор окажется в конце списка и хост выберет его в последнюю очередь; у бедняги будет время на то, чтобы прийти в себя.
Как мы видим, структуры данных эталонного хоста IPv6 не только привязаны к определенному сетевому интерфейсу, но и логически связаны между собой , как это схематически показано на рис. 5.23.
(рис 5.23) Структуры данных эталонного хоста IPv6 и связь между ними
Прямая связь заключается в следующем:
Обратная связь не менее важна и сводится вот к чему:
если NUD обнаруживает, что сосед стал недоступен, то это повод удалить из кэша DC все записи, ссылающиеся на него как на следующий шаг;
Это позволит хосту адаптироваться как к сбою маршрутизатора, так и к изменению топологии канала, когда бывший сосед становится доступен только через маршрутизатор.
Как именно хост примет во внимание недоступность маршрутизатора, зависит от его алгоритма управления списком маршрутизаторов по умолчанию. Принципиальный алгоритм [§6.3.6 RFC 4861] рекомендует только, чтобы, во-первых, заведомо или вероятно доступные маршрутизаторы получали приоритет и, во-вторых, в отсутствие заведомо или вероятно доступных маршрутизаторов список перебирался по кругу. Смысл этого алгоритма в том, чтобы не выбрать снова сбойный маршрутизатор до тех пор, пока хост не попытает счастья со всеми остальными пунктами списка.
Сведения о доступности маршрутизаторов поставляет механизм NUD, и может так оказаться, что в данный момент полной записи NC ни об одном из них нет. Это вовсе не значит, что все маршрутизаторы недоступны, — просто стали недоступны те из них, которыми хост до сих пор пользовался. Это повод проверить на работоспособность остальные маршрутизаторы в списке. Отсюда и возникает перебор списка по кругу.
Подведем итоги. В §5.1 мы "сконструировали" механизм розыска соседей, однако еще не сформулировали правил, когда именно узел IPv6 вправе считать удаленный адрес соседским и поводить его розыск. А розыск соседа, который соседом не является — это все равно, что охота на несуществующую черную кошку в темной комнате. Поэтому сейчас нам пришлось целенаправленно поработать над этим вопросом. В процессе мы пересмотрели простую модель канала и подсети, принятую в IPv4, и признали ее слишком ограниченной и не отвечающей требованиям современных канальных технологий. Взамен мы сформулировали для IPv6 принципиально иную модель, в которой маршрутизаторы координируют передачу данных хостами по каналу и за его пределы, а хосты, в свою очередь, обращаются к маршрутизаторам за сведениями о топологии канала, а не пытаются самостоятельно вычислить ее. Затем мы "создали" довольно простой протокол в рамках ND, который обеспечивает работу канала IPv6 согласно этой модели.
Исторической справедливости ради заметим, что хост IPv4 тоже мог следовать подобной линии поведения, когда у него не было полноценной таблицы маршрутов, а вместо нее были список маршрутизаторов по умолчанию и кэш маршрутов, уточняемый переадресовками [§3.3.1.2 RFC 1122]. Тем не менее, критерий соседства IPv4 был задан вполне однозначно и отвечал модели, когда все адреса в подсети, и только они, принадлежат соседями [§3.3.1.1 RFC 1122].
Как вы думаете, насколько оригинальна наша модель? Конечно же, ничто не ново под луной, и на ту же самую модель давно опирается стек протоколов ISO. В частности, взаимодействие хостов (конечных систем, ES) и маршрутизаторов (транзитных систем, IS) в нем обеспечивает протокол ES IS [ISO 9542; предыдущая версия доступна как RFC 995].
Подход ES IS к решению данной задачи можно даже признать более изящным и логически завершенным. Он не сводится к одним только переадресовкам, а включает в себя периодические объявления, которыми ES и IS извещают друг друга о своем существовании. Поэтому некое подобие розыска соседей в ES IS необходимо, только когда на канале нет ни одной IS. Даже в этом случае ES-источник не тратит время на предварительный розыск соседа, а просто шлет пакет с данными по групповому канальному адресу "все ES". ES-адресат, если он действительно подключен к каналу, принимает пакет в обработку и заодно высылает ES-источнику объявление, содержащее его индивидуальный канальный адрес. Тогда ES-источник кэширует канальный адрес соседа, и последующие пакеты уже идут по нему, а не группе "все ES".
Таким образом, ES по умолчанию полагает, что все адреса "на канале". В противоположность этому, хост IPv6 обязан по умолчанию считать, что "вне канала" находятся все адреса, кроме внутриканальных. Когда нет ни маршрутизатора, ни ручной настройки списка префиксов, хосты IPv6 могут обмениваться индивидуальными пакетами, только используя внутриканальные адреса, так как в этих условиях все остальные индивидуальные адреса находятся "вне канала" и потому недоступны.
Любопытно, что ранние стандарты IPv6 следовали модели ES IS, в которой все адреса по умолчанию были "на канале", однако впоследствии от нее полностью отказались [RFC 5942], потому что за ее внешней простотой скрываются различные осложнения [RFC 4943].
Для общего знакомства со стеком протоколов ISO и, в частности, с протоколом ES IS мы настоятельно рекомендуем замечательную книгу Ради
В завершение рассмотрим любопытный пример того, как отличается практика управления подсетями в IPv4 и IPv6. Как мы помним, в IPv4 была вполне допустима конфигурация сети, когда из подсети был "вырезан" префикс большей длины и назначен другому каналу. Скажем, префикс 192.0.2.0/24 назначен каналу К1, а 192.0.2.96/27 — каналу К2, как это показано на рис. 5.24.
(рис 5.24) Удаленный под-префикс в IPv4
Маршрутизаторы М1 и М2 естественно работали с такой конфигурацией, потому что префикс канала К2 был точнее, чем префикс канала К1, и диапазон адресов 192.0.2.96–192.0.2.127 однозначно маршрутизировался в направлении канала К2, тогда как остальные адреса вида 192.0.2.X оставались на канале К1. Тем не менее, хосты канала К1 не могли передавать пакеты хостам канала К2, потому что они разыскивали их на канале К1, вместо того чтобы обратиться к маршрутизатору М1. Обычным решением этой проблемы было или включить ARP proxy на маршрутизаторе М1, или распространить точный маршрут на 192.0.2.96/27 через М1 по всем хостам канала К1, вручную или протоколом маршрутизации.
Как бы мы стали решать ту же проблему в IPv6? Допустим, каналу К1 назначена подсеть 2001:DB8:1:2::/64, а каналу К2 — 2001:DB8:1:2:3:4::/96, как показано на рис. 5.25. Прежде всего, мы не можем не заметить, что длина префикса подсети, отличная от 64, нарушает адресную политику IPv6 из §2.6, хотя и не вступает в противоречие с базовым протоколом, так что назначение такого префикса остается на совести администратора сети.
(рис 5.25) Удаленный под-префикс в IPv6
Что же касается технического решения, то здесь мы видим, как минимум, три альтернативных подхода на основе стандартных механизмов IPv6:
Пусть читатель найдет способ составить такой список префиксов, используя минимально возможное число элементов. (Начните с доказательства того, что минимально необходимое число префиксов в таком списке равно 32. Затем попробуйте доказать, что кратчайшее решение — единственное. По ходу вы поймете, как устроен этот список.)
В нашей модели компьютерных сетей пакеты не перелетают с узла на узел волшебным образом, а путешествуют по каналам. Пока мы работаем на уровне IP, непосредственная передача пакета напрямую, "из рук в руки", возможна только узлу, который подключен к тому же каналу, что и наш локальный узел.
Нам удобнее думать о сетевом взаимодействии узлов, поставив себя на место одного из них и назвав его локальным узлом. Тогда все прочие узлы — удаленные.
Сам процесс передачи пакета по каналу может быть сколь угодно сложным и даже рекурсивно вызывать уровень IP, как это происходит в туннелях. Однако благодаря сознательному и целенаправленному разделению уровней в сетевой архитектуре это происходит прозрачно и выглядит для уровня-потребителя как один элементарный акт.
Чтобы нам вскоре не пришлось опровергать наши собственные утверждения, мы сразу же должны оговорить, что подключение узлов к общему каналу — это необходимое, но еще недостаточное условие для прямой передачи пакета между ними. Дело в том, что существуют "неклассические" каналы, в которых не все пары узлов могут обмениваться пакетами напрямую. Подробному обсуждению таких каналов в контексте IPv6 мы посвятим §5.2, а пока что давайте предположим, что наш локальный узел А априори знает, что он может передать пакет узлу Б напрямую, используя определенный канал К. Если это так, то узлы А и Б — соседи (neighbors) по каналу К. Пока наше воображение еще не смущено "неклассическими" каналами, мы можем позволить себе упрощенную картину, где соседи А и Б соединены каналом "точка-точка" — например, старым добрым PPP — или же широковещательным каналом — скажем, привычной локальной сетью Ethernet. Эти хрестоматийные случаи иллюстрирует рис 5.1(рис 5.1) Узлы-соседи по широковещательному каналу (слева) и по каналу "точка-точка" (справа)
Тем не менее, давайте с самого начала иметь в виду, что, с математической точки зрения, отношение соседства коммутативно, но не транзитивно: если узел Б — сосед узла А, то узел А — сосед узла Б; но если узлы А и Б соседи и узлы Б и В соседи, то это еще не значит, что узлы А и В тоже соседи.
Коммутативность соседства предполагает, что канал двунаправленный. Само понятие соседства нам нужно для разработки механизмов, ориентированных на двунаправленные каналы, так что однонаправленные каналы мы рассматривать не станем.
Еще одно свойство соседства, кажущееся самоочевидным, — это его рефлексивность: всякий узел является собственным соседом и не нуждается в маршрутизаторе, чтобы разговаривать сам с собой. Но в действительности оно не возникает из воздуха, а реализовано с помощью сетевого интерфейса типа "петля" и связанного с ним виртуального канала из одного узла, на котором и возникает этот вырожденный случай соседства. Когда эта схема дает сбой, возникают артефакты, такие как разговор узла PPP с самим собой через удаленного соседа.
Теперь подумаем, как локальный узел А обращается к соседу Б. Использует ли он для этого титул, кличку или радиопозывные? Конечно же, нет. Вместо этого узел А применяет адрес IP, присвоенный интерфейсу узла Б. Если у узла Б несколько интерфейсов, то мы имеем в виду, конечно же, один из подключенных к общему каналу К.
Таким образом, узел IP, намереваясь передать индивидуальный пакет, вначале знает только адрес IP удаленного соседа. Так как сосед может быть не только адресатом пакета, но и маршрутизатором, его называют "следующий шаг" (next hop), тем самым подчеркивая, что он может быть не последним на пути пакета.
Следующий шаг пакета от создавшего его хоста иногда называют "первый шаг" (first hop). На самом деле, эта деталь редко имеет значение, так что первый шаг пакета — всего лишь частный случай следующего шага.
Однако, чтобы на самом деле передать пакет дальше, уровень IP должен обратиться к канальному уровню, а тот использует только канальную адресацию и ожидает канального адреса соседа, будь то в явном или неявном виде. Поэтому локальный узел должен найти среди всех соседей по данному каналу именно того, кто обладает адресом следующего шага, и узнать его канальный адрес. Эта процедура известна нам как разрешение адресов (address resolution).
Если канал — типа "точка-точка", то это тривиальная задача, поскольку явный адрес IP и неявный канальный адрес единственного соседа заранее известны.
Напомним себе и читателям, что в определенных условиях бывает достаточно неявной адресации. Так, в канале "точка-точка" явная канальная адресация излишня, потому что любая передача в этот канал будет принята ровно одним узлом, которому она и предназначалась. Можно сказать, что адрес соседа по каналу "точка-точка" звучит как "вон тот узел на другом конце провода". Поэтому, например, присваивать адреса MAC интерфейсам на концах канала "точка-точка" было бы избыточным, хотя на это нет принципиального запрета.
Если же канал многоадресный, то задача существенно затрудняется; ее решение зависит от свойств этого канала. В худшем случае, когда нет
никаких вспомогательных механизмов, поможет только заданная вручную таблица соответствия между адресами сетевого уровня и канальными
адресами. Но, к счастью, такой случай практически не встречается, а зато мы довольно часто сталкиваемся с каналами, где есть возможность
обратиться сразу ко всем соседям и спросить, кому принадлежит искомый сетевой адрес. Эта идея, представленная на рис. 5.2, открывает нам
путь к автоматизированному розыску
Этим свойством, когда узел может одновременно обратиться ко всем соседям, обладают не только широковещательные каналы, такие как
Ethernet. Скажем, канал "точка-точка" тоже поддерживает широковещательную и групповую адресацию, поскольку сосед заведомо один.
Что же касается широковещательного канала, то он дает более сильные гарантии: все подключенные к нему узлы, независимо от их числа, —
попарно соседи, и вдобавок между ними возможно широковещание.
Хотя на первый взгляд может показаться, что разрешение адресов и розыск соседей — одна и та же задача, на самом деле они перекрываются, но не совпадают. Розыск соседей, в общем случае, можно вести по любым критериям, а не только по сетевому адресу. Например, узел может спросить у своих соседей: "Эй, кто из вас — маршрутизатор по умолчанию?" — или: "У кого это из вас жужжит вентилятор?" С другой стороны, разрешение адресов не всегда требует розыска соседей. Так, мы обсудили в §4.1.1, что разрешение групповых адресов IPv4 и IPv6 в групповые же адреса MAC происходит локально, простой подстановкой битов. Розыск не потребуется и тогда, когда требуемые сведения заранее внесены в локальную конфигурацию — то, что мы знали в IPv4 как статический ARP (static ARP).
История вычислительных сетей знает и другие примеры локального разрешения адресов. Так, в адресе IPX идентификатор интерфейса всегда совпадал с его канальным адресом MAC, так что найти канальный адрес соседа можно было, опустив те старшие биты адреса IPX, где находился префикс подсети.
Это логическое разделение полезно иметь в виду, чтобы наше решение не вышло узким и ограниченным. Тем не менее, первой прикладной задачей у нас стоит именно разрешение индивидуальных адресов IPv6 в канальные адреса, когда узел А спрашивает: "Дорогие соседи, кому из вас принадлежит сетевой адрес Б?" — и получает в ответ канальный адрес искомого соседа, если тот на месте.
Нетерпеливые читатели могут найти полный список функций, которые выполняет розыск соседей IPv6, в [§3 RFC 4861]. Но не стоит торопиться, ибо мы придем к этим функциям естественным путем.
Для начала мы рассмотрим уже готовое решение из нашего арсенала: ARP. Этот протокол достаточно гибок, чтобы работать с разными типами канальных и сетевых адресов, так что мы могли бы легко приспособить его и для нужд IPv6. Тем не менее, у нас есть повод, чтобы отказаться от ARP и создать новый механизм розыска соседей IPv6. В вину ARP мы поставим его… широковещание. Как так? Ведь мы сами только что сказали, что широковещание открывает нам светлый путь к розыску соседей! Не подлежит сомнению, что это была плодотворная идея. Но, чтобы перейти "от идеи к модели", нам надо учесть современные возможности каналов. В самом начале они поддерживали только два режима передачи, широковещательный и индивидуальный; однако с тех пор появился и прочно утвердился еще один режим, групповой.
В чем широковещательный режим проигрывает групповому? Прежде всего, у широковещания нулевая избирательность. Представим себе канал в смешанной сети, где одни узлы поддерживают только IPv4, другие — только IPv6, третьи — только ISO и т.д. Всякий раз, когда узел IPv4 посылает в такой канал запрос ARP, его принимают, анализируют и только затем отбрасывают все те узлы, которым вообще нет дела до IPv4; их вычислительные ресурсы расходуются при этом совершенно впустую, а в большой сети непроизводительный расход "виртуальной энергии" достигает заметной величины.
Ходит легенда о больших плоских ЛВС, настолько перегруженных широковещательным трафиком, что только самый современный и мощный компьютер может справиться с его приемом. Если же компьютер слабоват, то он практически повиснет, пытаясь принять, чтобы тут же отбросить, бесконечный поток не интересных ему широковещательных кадров.
Кстати, потребление электроэнергии компонентами и узлами сети тоже зависит от того, насколько оптимизировано в ней распространение трафика.
Это положение можно было бы значительно улучшить, если бы розыск соседей перешел на групповое вещание. В простом канале типа "общая шина" нежелательный групповой трафик отфильтруют сетевые адаптеры, а в современной коммутируемой ЛВС он даже не попадет в те сегменты, где его никто не ожидает. Вот нам задача номер один.
Например, коммутируемая ЛВС Ethernet сможет оптимизировать групповой трафик, если в ней применяется "подслушивание IGMP/MLD" (IGMP/MLD snooping) [RFC 4541] (см. §6.4) или специализированный протокол GMRP [IEEE 802.1ak 2007].
В IPv4 существовала группа "все узлы данной подсети", 224.0.0.1, а ей отвечал определенный групповой канальный адрес. Например, в Ethernet это был адрес MAC 01-00-5E-00-00-01. Но, увы, построить на этом стандартный механизм розыска соседей было нельзя, потому что поддержка группового вещания в IPv4 так и осталась необязательной [§3.3.7 RFC 1122].
Теперь же, в IPv6, групповое вещание — обязательная возможность (см. §2.8), и мы могли бы основать розыск соседей на групповом адресе "все узлы канала", FF02::1. Это сразу ограничило бы круг вовлеченных в него узлов только теми, что поддерживают IPv6. Но не можем ли мы придумать решение еще лучше?
Подсказкой нам послужит внушительная длина адреса IPv6. А что если мы закодируем в групповом адресе самую характерную часть искомого индивидуального адреса? Положим, канал объединяет какое-то число узлов IPv6. Их интерфейсам, смотрящим в этот канал, назначена одна и та же подсеть, но идентификатор интерфейса у каждого свой. Кроме того, мы заранее знаем, что длина этого идентификатора 64 бита. Значит, мы могли бы перенести их во внутриканальный групповой адрес с определенным префиксом, назначенным для этой цели. Допустим, если бы это был префикс FF02::/64, то из индивидуального адреса 2001:DB8:1:2:3:4:5:6 (И) мы получили бы групповой адрес FF02::3:4:5:6 (Г) . Теперь узел Б, владелец адреса И, должен вступить в группу Г и принимать запросы о розыске соседей. Его сосед А, разыскивающий адрес И, пошлет запрос по адресу Г и прицельно попадет запросом в узел Б, потому что в данной подсети ни у кого больше нет такого идентификатора интерфейса.
На словах звучит эффектно; а что произойдет в действительности? Прежде всего, узел А должен преобразовать адрес Г в групповой канальный адрес. Эта процедура не представляет сложности, потому что она выполняется локально (см. §4.1.1), однако часть битов идентификатора группы при этом, как правило, теряется. Так, например, при инкапсуляции Ethernet из группового адреса IPv6 в групповой адрес MAC переходят только младшие 32 бита. Кроме того, каждая группа занимает какое-то количество ресурсов в интеллектуальной ЛВС, оптимизирующей групповой трафик. Говоря проще, коммутаторам такой ЛВС приходится следить за "географией" каждой группы. Поэтому давайте ограничим общее число групп, которые могут возникнуть на одном канале при розыске соседей. Для этого пусть в групповой адрес Г попадают только избранные биты адреса И.
Но сколько и каких битов искомого адреса будет лучше всего взять? Мы уже выяснили, что они должны принадлежать идентификатору интерфейса. Когда администратор сети назначает эти идентификаторы вручную, то, скорее всего, он будет выбирать небольшие числа: 1, 2, 3… Если же идентификатор создается из адреса MAC 48 по рецепту §2.7, то мы должны учесть структуру этого адреса. Старшие 24 бита в нем — это OUI. Если, например, организация закупает для своей сети оборудование избранного производителя, то поле OUI в его адресах MAC окажется одинаковым (или будет принимать ограниченный набор значений). В то же время, младшие 24 бита в адресе MAC (добавочный идентификатор) носят куда более случайный характер, потому что в пределах одного OUI они уникальны, а каждый производитель назначает их независимо от своих конкурентов. Наконец, если у интерфейса есть адрес EUI-64, то добавочный идентификатор занимает в нем 40 бит. Их распределение зависит от политики производителя; в частности, при последовательном назначении старшие биты могут совпадать в пределах одной партии оборудования, а младшие будут разными. Таким образом, младшие 24 бита искомого адреса — это хороший выбор при разных политиках назначения идентификаторов интерфейсам. Тогда общее число групп, которые могут возникнуть в одном канале при розыске соседей, не превысит $$2^24$$. Да, это тоже немало, но, тем не менее, существенно меньше, чем первоначальные $$2^63$$.
Попытайтесь вспомнить или выяснить, почему $$2^63$$, а не $$2^64$$. (Подсказка: в формате "модифицированный EUI-64" тоже есть бит I/G, он же g, а розыск ведется только по индивидуальному адресу.)
Итак, мы вполне обоснованно решили, что в особый групповой адрес Г попадают только 24 младших бита искомого адреса И. Соответственно, заранее известный групповой префикс должен быть длиной 104 бита. На эту роль назначен общепринятый префикс FF02::1:FF00:0/104 [§2.7.1 RFC 4291]. Например, из нашего адреса И, то есть 2001:DB8:1:2:3:4:5:6, получится такой адрес Г: FF02::1:FF05:6. Общепринятый термин для внутриканального группового адреса, составленного по этому рецепту, — групповой адрес искомого узла (solicited-node multicast address).
Таково общепринятое английское название, тогда как русскоязычная терминология IPv6 еще только формируется. Обнаруженный нами
у других
Теперь мы знаем, по какому адресу надо слать запрос о розыске соседа. Это адрес группы искомого узла, объединяющей все интерфейсы канала с индивидуальными адресами, у которых младшие 24 бита такие же, как в искомом адресе. Хотя в такую группу могут попасть несколько интерфейсов с разными адресами, розыск соседей становится значительно прицельнее, нежели при широковещании или рассылке по групповому адресу "все узлы". Конечно, теперь всякий узел IPv6, назначая своему интерфейсу индивидуальный адрес, обязан вступить на этом интерфейсе в соответствующую группу искомого узла, чтобы принимать запросы от соседей. Когда же адрес удаляется, узел должен выйти из этой группы, если только на интерфейсе не остается других адресов с теми же 24-мя младшими битами. На этом наша первая задача решена.
На первый взгляд, число групп искомого узла, в которые узлу надо вступить на данном интерфейсе, равно числу индивидуальных адресов на этом интерфейсе. Но чуть более внимательный анализ обнаруживает известное нам свойство: разные индивидуальные адреса отображаются в один и тот же групповой адрес искомого узла, если в них совпадают 24 младших бита. Таким образом, аккуратный выбор идентификаторов интерфейса может сократить число групп искомого узла, которые придется слушать. Простейший подход сводится к тому, чтобы все идентификаторы интерфейса сделать одинаковыми, например, основанными на модифицированном EUI-64 этого интерфейса. Тогда адреса IPv6 этого интерфейса будут отличаться только префиксами подсети, а их группы искомого узла заведомо совпадут. Почему число таких групп стоит ограничивать? Во-первых, как мы сказали на с. 103, группы расходуют ресурсы сети. А во-вторых, современные сетевые адаптеры могут сами фильтровать групповой трафик, чтобы освободить шину и центральный процессор, но длина аппаратного фильтра в них ограничена. Когда узел слушает много разных групп и длина такого фильтра оказывается превышена, адаптер вынужден принимать весь групповой трафик в надежде, что центральный процессор разберется.
Группа искомого узла — это лишь частный случай применения группового вещания IPv6 для направленного розыска сетевых ресурсов. В общем случае, узел, обладающий ресурсом, вычисляет групповой адрес как функцию от имени ресурса и вступает в эту группу, а узел-искатель, зная имя ресурса, проводит то же самое вычисление и направляет по полученному групповому адресу запрос. К примеру, так можно разыскивать хост по его символьному имени: от имени хоста в заданной кодировке вычисляют хэш-функцию подходящей длины и добавляют к ее значению общепринятый групповой префикс. На этой идее уже основан один экспериментальный протокол для запроса информации об узлах IPv6 [RFC 4620].
Следующий вопрос заключается в том, как будет устроен протокол розыска соседей. Пока что мы не определились даже с его уровнем в стеке. Привлечение групповых адресов сетевого уровня означает, что новый протокол мы должны разместить над IPv6, а не рядом с ним, в отличие от IPv4 и ARP. По сути, розыск соседей — это один из аспектов управления IPv6, так что давайте поступим просто: пусть розыск соседей станет подмножеством ICMPv6. Это подмножество кратко обозначают ND (от neighbor discovery) [RFC 4861].
Как следствие, сообщения ND будут инкапсулированы в пакеты IPv6, а тогда на них смогут распространяться пакетные фильтры и прочие правила сетевой политики. Поэтому придется быть вдвойне осторожным, чтобы по ошибке не блокировать легитимный трафик ND и тем самым не "сломать" механизм разрешения адресов — ведь от его сбоя пострадает передача всего индивидуального трафика!
Такая позиция ND в стеке протоколов позволит применять его на любых каналах, способных инкапсулировать IPv6. Хотя сам по себе розыск соседей необходим далеко не всем типам каналов, например, он явно избыточен на каналах "точка-точка", мы создадим механизмы на основе протокола ND, которые будут уместны независимо от типа канала. Поэтому сразу выдвинем вот какое положение: ND будет работоспособен на всех каналах IPv6, которые поддерживают групповое вещание [§3.2 RFC 4861].
Означает ли наш выбор, что сообщения ND смогут пересекать границы каналов? Ведь в общем случае пакеты ICMPv6 подлежат маршрутизации наравне с другими пакетами IPv6. Между тем, из самого определения соседства следует, что пакеты ND должны распространяться только в пределах одного канала. Опасность здесь не только в утечке ND за границы канала, но и в проникновении фальшивых сообщений ND из-за этих границ.
Сможет ли адресат пакета ND узнать, что источник пакета — не его сосед? Если такой адресат не доверяет никому, то задача без дополнительных механизмов неразрешима. Но если адресат доверяет маршрутизаторам данного канала, то он может учесть, что всякий законопослушный маршрутизатор уменьшает значение "предельное число шагов" в заголовке IPv6 транзитного пакета. Сами по себе эти сведения еще ничем не помогают, так как адресат не знает, каким было начальное значение этого поля. Можем ли мы просто зафиксировать это значение? Допустим, мы решили, что начальное значение "предельного числа шагов" в пакетах ND всегда будет X. Теперь адресат пакета сравнивает видимое им значение с X и, если они совпадают, заключает, что пакет пришел от соседа. Но злоумышленник может как-нибудь узнать число шагов H до атакуемого узла и установить поле "предельное число шагов" равным , если отвечает соседству. Как помешать злоумышленнику? Если школьная арифметика помогла ему, то она поможет и нам! По условию задачи, , так как злоумышленник — не сосед атакуемого узла. (Если бы он оказался соседом, узлу пришлось бы совсем несладко!) Значит, злоумышленнику придется увеличить значение "предельного числа шагов" относительно X: . Единственный случай, когда он не сможет этого сделать, — это если значение X уже будет максимально возможным, а именно 255.
Этот трюк работает, только если маршрутизаторы правильно обрабатывают пакеты с нулевым значением "предельного числа шагов", как мы обсудили в §3.2. В противном случае декремент этого значения даст 255 согласно беззнаковой арифметике по модулю 256.
Атаки на ND со стороны соседей нам еще предстоит рассмотреть.
Обнаруженный нами трюк способен защитить от дистанционных атак не только ND и не только в среде IPv6. Ведь все, что ему нужно, — это поле "предельное число шагов" или "TTL" в сетевом заголовке. За свою универсальность этот трюк получил пышноватое название "обобщенный механизм безопасности на основе TTL" (Generalized TTL Security Mechanism), сокращенно GTSM [RFC 5082].
Другой пример протокола, который может выиграть от применения GTSM, — это BGP. Исторически идея GTSM появилась именно в применении к BGP [§4 RFC 5082], а затем ее обобщили и на другие случаи.
Конечно, GTSM не заменяет собой прочие механизмы сетевой безопасности, такие как фильтрация пакетов на границах канала. Просто любая хорошо построенная оборона обязана быть глубоко эшелонированной.
Итак, участники ND обязаны применять GTSM: источник пакета ND должен установить поле "предельное число шагов" равным 255, а его адресат обязан убедиться, что это значение сохранилось [§6.1, §7.1, §8.1 RFC 4861].
Теперь можно перейти к отдельным сообщениям ND. Чтобы провести разрешение сетевого адреса, достаточно двух видов сообщений, которые в первом приближении выглядят как запрос и ответ. Но, как мы помним, даже в ARP строгую последовательность "запрос-ответ" приходилось нарушать, когда узлу надо было послать "добровольный ARP" (gratuitous ARP), чтобы самому объявить соседям свой канальный адрес.
В ARP не имело значения, представлял ли собой добровольный ARP запрос или ответ, потому что алгоритм получателя ARP [RFC 826] проверял код операции только после того, как обновлял локальный кэш.
Не Конфуций ли сказал: если название неправильное, то и слова не повинуются? Поэтому давайте для начала исправим терминологию. Сообщение, которое несет сведения о сетевом и канальном адресах узла, мы назовем объявлением соседа (neighbor advertisement), сокращенно NA. Узел вправе выслать это объявление добровольно или по запросу. Другие же узлы могут запросить такое объявление, когда им это нужно, послав вызов соседа (neighbor solicitation), сокращенно NS.
Снова представим себе, что узел А хочет передать пакет своему соседу Б, но не знает его канального адреса. Теперь у узла А есть простая методика:
А что узел А должен делать после? Если он немедленно забудет канальный адрес Б, то указанную процедуру ему придется повторять на каждый пакет для соседа Б, а это будет совершенно неэффективно. С другой стороны, узел А не должен хранить канальный адрес Б в своей памяти вечно, потому что тот может впоследствии измениться, например, если в узле Б "на лету" сменят сетевой адаптер.
Подумайте, какая из возникших проблем была бы самой острой, если бы процедуру розыска соседа пришлось бы выполнять для каждого исходящего пакета? (Авторский вариант: возникновение задержки, равной ПКО (RTT) данного канала.)
Нашего опыта достаточно, чтобы сказать: здесь нужен кэш. Действительно, кэш соответствия между сетевыми и канальными адресами уже был в ARP. Политика управления им была весьма простой [RFC 826]:
В отсутствие дополнительных сообщений ARP, запись неизбежно удалялась по тайм-ауту, даже если тем временем протокол более высокого уровня, например, TCP, успешно вел обмен данными с узлом Б, чем подтверждал актуальность данной записи. Хуже того, при активном обмене данными с узлом Б широковещательная процедура ARP немедленно повторялась после удаления его записи, создавая паразитную нагрузку на других соседей. Так было потому, что между вышестоящими уровнями стека и ARP отсутствовала обратная связь.
Этот неоптимальный подход появился с одобрения [§2.3.2.1 RFC 1122] — см. п. 1. Хотя в том же разделе RFC были предложены и более
эффективные приемы работы с кэшем ARP, п. 1 распространился благодаря сетевому стеку BSD
Конечно же, уровни сетевого стека выше IP, например, транспортный уровень SCTP, TCP и UDP, обязаны быть только у хоста, тогда как маршрутизатору в теории достаточно продвигать транзитные пакеты IP.
Мы обязательно примем меры, чтобы ND не унаследовал этот недостаток. Сам же кэш канальных адресов был важной и полезной деталью; он нам необходим и сейчас. Назовем мы его просто: кэш соседей (neighbor cache), сокращенно NC.
Работу над NC мы начнем именно с того, что постулируем связь между ND и вышележащими уровнями в сетевом стеке хоста IPv6. Эта связь носит локальный характер и не выходит за пределы одного узла, поэтому ее можно реализовать по-разному. В простейшем случае, это будет просто вызов функции ядра. Главное, что теперь более высокий уровень в стеке способен сообщить модулю ND: да, я уверен, что данный адресат доступен. Например, если этим уровнем будет TCP, то он сможет утверждать доступность адресата, если от того поступят новые данные или новые квитанции (не дубликаты).
На первый взгляд, TCP делает необоснованный вывод; ведь прием пакетов вовсе не означает, что мы знаем верный канальный адрес их передатчика. Однако TCP — это протокол с обоюдным квитированием, и приход новых данных или квитанций возможен, только если удаленная сторона успешно получает наши квитанции или данные, соответственно.
Подобную обратную связь можно было реализовать и в ARP. П. 4 §2.3.2.1 RFC 1122 вполне допускает связь между вышележащими уровнями и модулем ARP, хотя и говорит о передаче отрицательных, а не положительных сигналов: протокол более высокого уровня сообщает модулю ARP, что данный узел недоступен, а модуль ARP удаляет запись об этом узле.
Подробному обсуждению тонкостей обратной связи между вышестоящими протоколами и ND посвящено [Приложение E.1 RFC 4861].
Однако у нас есть одно затруднение: вышестоящий уровень подтверждает доступность адресата, а не соседа. Это вполне закономерно: ведь выбор соседа происходит на уровне IP, в процессе маршрутизации исходящего пакета. Поэтому мы должны обеспечить логический переход от адресата к соседу.
Здесь под маршрутизацией мы понимаем выбор следующего шага пакета, а не продвижение транзитных пакетов (forwarding). В этом смысле маршрутизировать надо все исходящие пакеты, включая созданные на данном узле.
Если адресат окажется соседом локального узла, то модуль ND может сразу же обновить его запись NC. Но если адресат доступен только косвенно, через маршрутизатор, то перед модулем ND встанет такой вопрос: через какого соседа мы последний раз слали пакет данному адресату? Ответить на него поможет еще один кэш, который станет хранить самые свежие пары "адресат-сосед". Он так и называется: кэш адресатов (destination cache), кратко DC.
Также запись в кэше адресатов может хранить дополнительные сведения о данном адресате, например, текущую оценку величины PMTU. К этой идее мы придем естественным образом в §6.5.
Хотя модуль ND маршрутизатора не получает сигналов от вышележащих уровней стека по причине отсутствия оных, кэш адресатов может пригодиться и маршрутизатору. Например, при распределении трафика по равноценным путям (equal-cost multipath, ECMP) важно, чтобы все пакеты одного прикладного потока, такого как сеанс TCP, направлялись по одному пути. В противном случае в сети могут возникнуть трудно диагностируемые проблемы [RFC 2991]. Самая распространенная из них — это переупорядочение пакетов из-за того, что задержки разных путей всегда немного отличаются. Конечно, переупорядочение не фатально для TCP/IP, но, происходя постоянно, оно значительно понижает эффективность транспортных протоколов. Например, для механизмов управления перегрузкой TCP приход пакета вне очереди — это признак потери предыдущего пакета, а значит, перегрузки в сети [§3.2 RFC 5681]. Еще одна серьезная проблема — это видимые флуктуации PMTU, если PMTU вдоль разных путей отличается. Кэш адресатов поможет направить все потоки одного адресата по одному и тому же пути.
Чтобы нам стало понятнее, как возникшие структуры данных связаны между собой, приведем иллюстрацию — рис. 5.3. В качестве отправной точки мы включили в эту схему хорошо знакомую нам таблицу маршрутов. Однако давайте иметь в виду, что в §5.2 мы пересмотрим эталонную модель хоста IPv6 и придем к неожиданному выводу, что таблица маршрутов для него необязательна. С другой стороны, программная реализация может совместить все три логических структуры в одной [§5.1 RFC 4861], например, в базисном дереве (radix tree) по образу и подобию таблицы маршрутов BSD.
(рис 5.3) Связь между структурами данных в памяти узла: таблица маршрутов, DC и NC
Мы еще обсудим кэш адресатов в §5.2, а сейчас перейдем к структуре и жизненному циклу записи NC. Сама роль такой записи требует, чтобы ключом в ней выступил адрес IPv6 соседа; то есть в NC не может быть двух записей об одном и том же соседском адресе. В то же время, механизм ND даже не пытается выяснить, принадлежат ли разные адреса IPv6 одному соседскому интерфейсу или узлу, и поэтому, говоря "запись NC о соседе Б", мы подразумеваем запись об индивидуальном адресе Б, ставя условный знак равенства между узлом-соседом и его индивидуальным адресом. Это одно из допущений модели ND, которое, не будучи универсальной истиной, вполне работает в частном случае разрешения индивидуальных адресов IP в канальные адреса.
Говоря об использовании множественных подключений в §6.8, мы встретим случай, в котором принципиально важно учитывать, какие адреса IPv6 принадлежат одному и тому же удаленному узлу. Однако тот случай не будет иметь прямого отношения к розыску соседей.
В своем исходном состоянии запись NC о соседе Б просто не существует. Ее небытие продолжается до тех пор, пока у локального узла А впервые не возникнет необходимость передать пакет соседу Б.
Обратите внимание, как это отличается от поведения ARP. В кэше ARP запись создавалась по приходу любого сообщения ARP, чей сетевой адрес цели (target protocol address) принадлежал локальному узлу. Поэтому в кэше ARP могли возникать ненужные записи. Напротив, входящее объявление NA никогда не служит поводом создать новую запись NC.
Тогда узел А создает запись, но канальный адрес в ней еще неизвестен. Это состояние НЕПОЛНАЯ (INCOMPLETE). Чтобы
заполнить запись, узел А должен получить от Б объявление соседа. О своем желании он сообщает, направляя вызов соседа на групповой
адрес искомого узла, полученный из индивидуального адреса "Б". Дальше узлу А остается только ожидать. Если ожидание затянется
Для этого служит сообщение ICMPv6 тип 1, код 3 [§3.1 RFC 4443].
У записей в кэше ARP тоже было неполное состояние — по крайней мере, начиная с сетевого стека
Но вот, допустим, от узла Б своевременно поступило объявление соседа. Значит ли это, что сосед Б доступен и его канальный адрес можно с уверенностью использовать? Строго говоря, нет. Ведь у нас нет гарантии, что это объявление выслано в ответ на вызов узла А. Может быть, вызовы не доходят до Б из-за одностороннего сбоя в канале, но Б сам почему-то решил выслать объявление NA (ему это не возбраняется). Надежно различить эти два случая можно, если формат NA предусмотрит специальный флаг. Скажем, если он сброшен, то объявление добровольное, а если установлен — то по вызову. Обозначим этот флаг как S (solicited, по вызову).
По протоколу, узел Б устанавливает флаг S, только если сообщение NA адресовано персонально узлу А в ответ на запрос NS узла А. Именно эту информацию флаг S и доносит до узла А. Поэтому дополнительные проверки со стороны узла А, как то сверка адреса назначения NA, в этом случае избыточны.
Еще раз обратим внимание: хотя приход "непрошенного" объявления NA — это вполне допустимое событие, оно не приводит к созданию новой записи NC, а только влияет на уже существующую запись.
Теперь, в зависимости от состояния флага S в полученном NA, перед узлом А открываются два пути. Если флаг установлен, то канал между А и Б явно работает в двух направлениях, и записью NC можно смело пользоваться хотя бы некоторое время. В нее надо поместить канальный адрес Б, взятый из NA, и перевести запись в состояние ДОСТУПНАЯ (REACHABLE).
Какое время запись NC будет оставаться ДОСТУПНОЙ в отсутствие дополнительных сигналов? Чтобы избежать синхронизации соседей
между собой, это время надо сделать разным на разных узлах, например, случайным
Если же флаг S сброшен, то о доступности узла Б по-прежнему ничего не известно, но узел А все-таки узнал канальный адрес Б и может рискнуть передать по нему ожидающий пакет в канальном обрамлении. В этом случае узел А тоже сохраняет канальный адрес Б в записи NC, чтобы информация не пропала зря, но переводит запись в состояние ПРОСРОЧЕННАЯ (STALE), на котором мы остановимся подробнее буквально через несколько абзацев. В двух словах, сведения из ПРОСРОЧЕННОЙ записи имеют хороший шанс оказаться актуальными , но, тем не менее, нуждаются в подтверждении, чтобы запись стала ДОСТУПНОЙ.
Все состояния, в которых запись заполнена (в ней указан не только сетевой, но и канальный адрес), мы будем собирательно называть полными.
ДОСТУПНАЯ запись освежается по сигналу от вышестоящих протоколов, или же дополнительными объявлениями NA с S = 1 —
то есть сбрасывается тайм-аут записи. Но допустим, что такую запись
Так как в ND активная ДОСТУПНАЯ запись постоянно освежается, ее тайм-аут может быть существенно меньше, чем знакомое нам время жизни записи в кэше ARP. Так что не стоит удивляться, что простаивающие ДОСТУПНЫЕ записи NC становятся ПРОСРОЧЕННЫМИ гораздо скорее, нежели предсказывает наш опыт IPv4.
ПРОСРОЧЕННАЯ запись хранит сведения, в которых узел А не уверен. Без проверки пользоваться ими нежелательно, и поэтому запись остается ПРОСРОЧЕННОЙ ровно до тех пор, пока к ней не будет обращений. Но, с другой стороны, и обязательного тайм-аута у ПРОСРОЧЕННОЙ записи нет, потому что она содержит полезные сведения.
Проходит время, и вот узел А опять готов передать новый пакет соседу Б, запись о котором ПРОСРОЧЕННАЯ. Весьма вероятно, что
узлы А и Б находятся там же, где они были и раньше. Поэтому узел А шлет пакет по канальному адресу Б, взятому из записи. Он надеется,
что вскоре вышестоящий протокол подаст сигнал о доступности соседа Б. Такое событие узел А отмечает, переводя запись в состояние
ЗАДЕРЖКА (DELAY) и устанавливая ее таймер
А что если интервал этого таймера истек, а сигнала сверху по-прежнему нет? Даже тогда для записи в состоянии ЗАДЕРЖКА не все
потеряно: ведь, возможно, переданный пакет не предполагал никакого отклика, или он потерялся где-то дальше в сети. Чтобы разрешить
возникшую неоднозначность, узел А целенаправленно проверяет годность записи NC: он направляет вызов NS по индивидуальному адресу
узла Б, используя канальный адрес из записи, и переводит запись в состояние ИСПЫТАНИЕ (PROBE). И, наконец, если даже
после нескольких
Современные реализации IPv4 тоже освежают записи в кэше ARP по истечении тайм-аута, индивидуально обращаясь к соседям. Например, так
поступает ядро
Окончательная хронология не обновляемой и потому обреченной исчезнуть записи NC показана на рис 5.4(рис 5.4) Наглядная хронология устаревающей записи NC
Пока запись NC полная, но проходит проверку, в очереди узла А могут возникнуть прикладные пакеты, требующие передачи соседу Б. Должен ли узел А приостановить их отправку до тех пор, пока он не убедится в достоверности записи? На самом деле, пауза здесь не требуется и может быть даже вредна, так что узел А продолжает передавать пакеты соседу Б, используя канальный адрес из записи. Благодаря этому, у вышестоящих протоколов тоже появляется возможность освежить запись. Сигнал вышестоящего протокола переводит полную запись из любого состояния в состояние ДОСТУПНАЯ. Очевидно, что на несуществующие и НЕПОЛНЫЕ записи сигнал не действует, даже если он вдруг поступит, поскольку в таких записях нет информации о канальном адресе соседа.
Хотя начинали мы с простой экономии ресурсов канала и узлов-соседей, в результате у нас получился полнофункциональный механизм наблюдения
за состоянием соседей, которым локальный узел передает пакеты. Этот механизм называют выявление недоступности соседей
(neighbor unreachability detection), сокращенно NUD [§7.3 RFC 4861]. По сути, механизм NUD — это и есть конечный
автомат ND, а информация о доступности соседей — один из основных продуктов его работы. На текущий момент у нас готов упрощенный вариант
этого механизма, схематически показанный на рис 5.5(рис 5.5) Упрощенная диаграмма состояний ND
Более полную диаграмму состояний ND можно найти, например,
Ценой определенных усилий мы победили в ND недостатки ARP и теперь можем спокойно взглянуть на плодотворные идеи старого протокола, чтобы не потерять их в ND.
Первая идея состоит в том, что большинство запросов ARP, а теперь и вызовов NS открывает некий сетевой диалог. Иными словами, очень часто узел А проводит розыск соседа Б и передает ему пакет, а вскоре уже узел Б имеет что передать своему соседу А. Если А и Б — хосты, то это естественный стиль работы большинства прикладных протоколов. Если же один из них или оба — маршрутизаторы, то здесь проявляется не только двунаправленная работа прикладных протоколов, но и практическая симметрия маршрутов. Поэтому модуль ARP, получив запрос, предусмотрительно создавал запись о его источнике, если запрос касался локального узла: он предвидел, что скоро роли поменяются и источник запроса станет искомым соседом, получателем пакета IP.
В среде IPv6 намек на предстоящий диалог — это вызов NS. Если формат сообщения NS предусмотрит место для канального адреса источника (А), то получатель NS (Б) сможет сразу же создать о нем полную запись NC. По правилам NUD, эта запись — ненадежная, потому что проверена только односторонняя проходимость канала: А$$\to$$Б. Значит, такая запись заслуживает состояния ПРОСРОЧЕННАЯ. Это состояние записи вполне позволит сетевому диалогу начаться без задержки на еще один цикл ND между А и Б, когда узел Б станет разыскивать соседа А. Далее , если все будет в порядке, запись о соседе А перейдет в состояние ДОСТУПНАЯ по стандартной процедуре, не задерживая диалог.
Вторая идея заключается в том, что на розыск соседа не всегда откликается сам владелец искомого сетевого адреса. Искомый узел может находиться вне канала, а ответит за него маршрутизатор, у которого есть сведения о доступности искомого узла, как то маршрут к нему, более точный (specific), чем префикс подсети канала. Поскольку такой маршрутизатор отвечает от имени искомого узла и выступает посредником между искателем и искомым, то его обозначают популярным термином proxy. В частности, в ARP такой маршрутизатор назывался ARP proxy. Задача подобного proxy — привлечь к себе трафик для искомого узла, чтобы затем продвинуть его согласно таблице маршрутов, как показано на рис. 5.6. Поэтому proxy должен ответить, что искомому сетевому адресу отвечает его собственный канальный адрес. Особенность IPv6 здесь только в том, что ND proxy должен вступить в группу искомого узла, отвечающую адресу "Б", чтобы принимать вызовы NS к Б и отвечать на них объявлениями NA от имени Б.
(рис 5.6) Идея: ND proxy отвечает за другой узел
Если ND proxy отвечает от имени многих узлов, например, целой подсети, ему может пригодиться режим работы сетевого интерфейса, когда тот принимает все групповые кадры подряд (allmulti). Однако это может вызвать осложнения в коммутируемой ЛВС, использующей сообщения IGMP или MLD (§6.4) для изучения географии групп IP. В такой ЛВС трафик группы просто не дойдет до портов, из которых не поступало уведомления о вступлении в данную группу.
Функция ND proxy должна быть настраиваемой. Как минимум, ей требуется "выключатель", потому что по умолчанию она должна быть выключена.
А как быть, если искомый узел — мобильный и может находиться то по соседству, то вне данного канала, не меняя при этом своего адреса? Сохраним наши обозначения: пусть Б — это искомый узел, тогда как А — наш локальный узел, который хочет передать пакет непосредственно узлу Б, полагая при этом, что Б — его сосед. Этот сценарий представлен на рис. 5.7.
(рис 5.7) Мобильность узла, за которого отвечает ND proxy
Пусть сначала узел Б находится в другой части сети. Узел А начинает розыск узла Б запросом ARP в IPv4 или вызовом NS в IPv6. От имени узла Б отвечает proxy и сообщает свой канальный адрес. Узел А думает, что это ответил его сосед Б, и передает пакет по канальному адресу proxy. Так proxy перехватывает пакеты, предназначенные узлу Б, и поступает с ними согласно своим текущим настройкам. В это время узел Б вообще не видит запросов ARP или вызовов NS, которые посылает узел А, потому что узлы А и Б на самом деле не являются соседями.
Теперь допустим, что узел Б незаметно перемещается на данный канал и действительно становится соседом узла А, как показано на рис. 5.8. В этом случае он уже самостоятельно отвечает на запросы (вызовы) узла А. Но proxy тем временем продолжает отвечать от его имени, потому что ничего не знает о перемещении узла Б. Это значит, что на каждый запрос узла А придет два ответа (объявления), сообщающих разные канальные адреса искомого узла Б! Какой окажется запись об узле Б в кэше узла А, если он успешно получит оба ответа? В ARP все решает ее величество фортуна: окончательный вид записи зависит от порядка, в котором поступят ответы, а он, можно сказать, случайный. "Победит" ответ, пришедший последним: именно его канальный адрес останется в записи. Таким образом, примерно в половине случаев в запись об узле Б попадет канальный адрес proxy, и пакеты пойдут туда, где узла Б в данный момент нет. Проблема налицо, и нам обязательно надо исправить ее в ND, потому что сегодня мобильные узлы получают все большее распространение.
Точнее, в IPv4 один или несколько первых пакетов пойдут по канальному адресу из ответа ARP, пришедшего первым, а затем придет другой ответ, и последующие пакеты пойдут по канальному адресу из него.
(рис 5.8) Мобильность приводит к сбою: трафик уходит не туда
К счастью, перед нами всего два уровня в ранжире узлов, "борющихся" за искомый адрес. Первый, с наибольшим приоритетом — это действительный владелец адреса Б. По определению индивидуального адреса, владелец у него ровно один. Второй уровень — это proxy, которых может быть и несколько. Относительный приоритет одних proxy перед другими мы определять не будем, так как в данном случае proxy — это вспомогательный механизм, и его не стоит усложнять сверх меры. Поэтому нам достаточно одного бита в формате NA, который и укажет, пришло ли объявление NA от владельца адреса или от proxy.
Теперь давайте подумаем, как получатель обязан реагировать на то или иное значение этого бита, чтобы получился желаемый результат. Нам необходимо, чтобы в итоге "выигрывал" владелец адреса, независимо от последовательности объявлений:
То есть объявление владельца вытесняет данные, полученные от proxy, но не наоборот. Обеспечить это можно было бы, снабдив запись в NC атрибутом: заполнена ли она по объявлению владельца или proxy. Но нельзя ли обойтись без дополнительного атрибута? Если объявление послал владелец, то оно безусловно обновляет запись в кэше, и происхождение этой записи неважно. Если же объявление исходит от proxy, а полная запись с другим канальным адресом уже существует в NC, то достаточно воспользоваться конечным автоматом NUD и просто перевести запись в состояние ПРОСРОЧЕННАЯ, не изменяя других ее полей.
Пожалуйста, не забывайте, что, в отличие от ARP, запись в кэше соседей ND никогда не создают по приходу объявления NA. Объявление NA может повлиять только на уже существующую запись, будь то полную или НЕПОЛНУЮ.
Став ПРОСРОЧЕННОЙ, запись проходит процедуру проверки. Если указанный в ней канальный адрес по-прежнему доступен, то запись просто возвращается в состояние ДОСТУПНАЯ. Так произойдет, если запись указывает на "живого" владельца искомого адреса, или же на "живой" proxy, который предлагает доступ к владельцу искомого адреса. В противном случае запись исчезнет, а узел А повторит процедуру ND с самого начала. Это случится, в частности, когда узел Б снова уйдет с канала и станет доступен только через proxy.
Поскольку все proxy равноправны, в рамках ND нет способа определить, какой именно proxy дает доступ к узлу Б, когда тот вне канала и за него отвечают несколько proxy. Эту проблему придется решать надлежащей настройкой самих proxy.
Обратите внимание: механизм NUD следит только за состоянием подлинных соседей. Так, если запись NC об узле Б указывает на proxy, то ее состояние — это состояние proxy, а не самого узла Б. О доступности узла Б в этом случае NUD информации не предоставляет, потому что он ограничен рамками канала и не рассчитан на слежение за сквозной доступностью. Эту архитектурную особенность стоит иметь в виду, даже если вы не собираетесь применять ND proxy на практике.
Окончательный смысл нового флага в сообщении NA сводится к тому, замещать ли уже существующую полную запись. Поэтому данный флаг мы обозначим как "замещение" (override), сокращенно O. Если в принятом сообщении NA флаг O установлен (O = 1), то сведения из этого сообщения имеют приоритет перед записью в NC. Канальный адрес из сообщения немедленно перемещается в запись. Сама запись становится ДОСТУПНОЙ, если сообщение по вызову (S = 1), или ПРОСРОЧЕННОЙ, если оно добровольное (S = 0). Если же флаг O сброшен (O = 0), то новый канальный адрес в сообщении NA не приводит к немедленному обновлению записи NC. Вместо этого ДОСТУПНАЯ запись просто становится ПРОСРОЧЕННОЙ; в других полных состояниях запись вообще остается без изменений, потому что она уже проходит проверку [§7.2.5 RFC 4861]. После дополнения этими правилами конечный автомат NUD приобретает вид, показанный на рис. 5.9.
(рис 5.9) Диаграмма состояний ND с учетом флага O и смены канального адреса
Теперь, благодаря флагу O и связанным с ним правилам обновления канального адреса, мы можем отразить в ND еще одну полезную идею ARP. Если у интерфейса данного узла менялся канальный адрес, то узел высылал широковещательный "добровольный ARP", в котором разыскивал собственный сетевой адрес или отвечал о нем сам себе. Это вызывало немедленное обновление существующих записей у его соседей — точнее, у тех из них, до кого добровольный ARP успешно дошел. Почему происходило обновление, мы уже обсудили: по правилам ARP, получатель сообщения первым делом искал сетевой адрес источника в своем кэше и обновлял его канальный адрес [RFC 826]. Хотя этот механизм вполне работал, сейчас давайте позаботимся, чтобы каждый тип сообщения ND делал, по возможности, свое дело. К счастью, узел IPv6 уже вправе выслать объявление NA, когда ему этого захочется. Единственная обязанность узла, когда объявление добровольное, — это сбросить в нем флаг S.
Добровольный запрос ARP служил также и для того, чтобы обнаруживать конфликты адресов IP, но об этой функции мы поговорим в §5.4.1.
Поэтому, когда происходит смена канального адреса, узел IPv6 может выслать добровольное объявление NA о своем новом канальном адресе.
Подходящими флагами в этом объявлении будут сброшенный флаг S (S = 0) и установленный флаг O (O
= 1), поскольку объявление не по запросу, однако его источник самый авторитетный. А по какому адресу следует выслать такое объявление?
Так как данный узел не знает, кто из его соседей хранит в NC запись о нем, объявление придется выслать по групповому адресу "все
узлы канала", FF02::1. Чтобы оно наверняка не потерялось, его можно повторить несколько раз
На этом мы исчерпали первую порцию задач, связанных с розыском соседей в IPv6, и можем наконец-то собрать вместе требования к формату сообщений NS и NA.
По нашему плану, центральный элемент сообщения NS — это адрес IPv6 искомого соседа, о котором происходит запрос. Нам уже сейчас нетрудно предвидеть, что сетевой или канальный адрес соседа, о котором идет речь в данном сообщении, будет элементом в формате многих сообщений ND. Поэтому давайте называть его одним термином: адрес цели (target address). В сообщении NS (рис. 5.10) центральное место займет сетевой адрес цели, то есть ее адрес IPv6 [§4.3 RFC 4861].
Флаги в сообщении NS нам не понадобились. Зато выше, на с. 112, мы запланировали, что в целях оптимизации сообщение NS может включать в себя канальный адрес источника (при этом сетевой адрес источника находится в заголовке IPv6). Насколько эта информация необходима в теле NS и есть ли у нее другие применения?
Мы настолько сконцентрировались на поведении узла-искателя, что совсем забыли об искомом узле и упустили из виду вот какую важную деталь: чтобы ответить индивидуальным объявлением NA на вызов NS, необходимо сперва узнать канальный адрес узла-искателя. Очевидно, что применить ND для этой цели нельзя, так как протокол зациклится и возникнет тупиковая ситуация. Поэтому верным решением будет указать канальный адрес узла-искателя, источника NS, непосредственно в самом NS. Конечно, канальный кадр, доставивший вызов NS, должен содержать в себе адрес источника, но с уровня IP доступ к канальному заголовку может быть затруднен ввиду программных ограничений стека. Поэтому вполне оправдано повторить канальный адрес источника в теле вызова NS.
Таким образом, получается, что типичное сообщение NS не только может, но и должно содержать в себе канальный адрес источника. Из этого правила будет всего одно серьезное исключение. Когда мы будем говорить в §5.4 об автоматической настройке адресов, у нас возникнет вызов NS с неопределенного адреса и без канального адреса источника. Уже и сейчас понятно, что такой особенный вызов NS понадобится, чтобы обойти очередную "проблему курицы и яйца" на нашем пути.
Еще есть один случай, когда канальный адрес источника в вызове NS рекомендован, но не обязателен: когда вызов NS индивидуальный [§4.3 RFC 4861]. Ведь такой вызов шлют соседу, когда его запись NC уже существует в заполненном виде, а значит, велик шанс, что у соседа тоже есть запись NC о нашем локальном узле [§7.2.2 RFC 4861]. Сосед должен был создать такую запись по приходу от нас самого первого вызова NS, который был групповым и содержал канальный адрес источника.
В итоге, канальный адрес источника — это не вполне обязательный элемент NS, а значит, нам понадобятся опции в формате ND.
(рис 5.10) Вызов соседа — NS
Пусть общий вид опций ND (рис. 5.11) будет основан на уже знакомом нам формате TLV: тип (1 байт), длина (1 байт), а затем переменное количество данных, формат которых зависит от типа [§4.6 RFC 4861]. Длина опции ND измеряется в 8 байтных словах и, разнообразия ради, включает в себя байты типа и длины, так что нулевое значение длины свидетельствует о сбое. Узнать о наличии опций в сообщении ND можно, определив, где находится его конец: если он совпадает с концом фиксированной части формата, то опций нет, а иначе они присутствуют. Как определить границы сообщения ND, мы уже сказали в §4.3; ведь ND — это подмножество ICMPv6.
(рис 5.11) Общий вид опции ND
В уже потребовавшейся нам опции "канальный адрес источника" (Source Link-Layer Address Option, SLLA или SLLAO) роль данных играет канальный адрес, а ее тип равен 1 [§4.6.1 RFC 4861]. Такой формат опции SLLA представлен на рис. 5.13. В частности, на канале Ethernet длина данных будет равна шести байтам, а всей опции — единице (8 байт), как показано на рис. 5.10. Поэтому, к примеру, адрес MAC 02 03 04 05 06 07 будет инкапсулирован в эту опцию так: <0x01, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07>.
Предполагается, что у каждой канальной технологии есть стандарт RFC, где зафиксированы детали работы IPv6 поверх каналов этого типа. Точный формат опций ND "канальный адрес" — одна из таких деталей. Например, см. [§6 RFC 2464] для Ethernet.
В отличие от ARP, ND не указывает явным образом тип канального адреса. Ведь он однозначно следует из типа канала,
откуда пришло сообщение ND.
(рис 5.12) Опции SLLA (тип = 1) и TLLA (тип = 2) — общий формат
(рис 5.13) Опции SLLA (тип = 1) и TLLA (тип = 2) для Ethernet
Сообщение NA содержит сведения о соседе, а именно его сетевой и канальный адреса. Согласно нашей терминологии, это адреса цели. Хотя сетевой адрес источника сообщения приведен в заголовке IPv6, он может и не совпадать с сетевым адресом цели, так как сообщение мог послать proxy, а не сам искомый узел. Чтобы proxy не пришлось "подделывать" адрес в заголовке IPv6, мы поместим сетевой адрес цели отдельно, в тело сообщения NA [§4.4 RFC 4861].
Форматы NS и NA могут стать довольно похожими, если мы приложим к этому немного усилий ради упрощения будущих реализаций. Для этого пусть канальный адрес цели разместится в опции, а не в фиксированной части сообщения NA. Когда искомый узел или proxy отвечает на групповой вызов NS, он обязан включить эту опцию в объявление NA, чтобы сообщить канальный адрес цели. В то же время, когда узел отвечает на индивидуальный запрос NS, он вправе предположить, что источник запроса уже располагает правильным канальным адресом цели; в этом случае канальный адрес цели в объявлении NA можно опустить, хотя особой выгоды это не принесет. Соответствующая опция — "канальный адрес цели" (Target Link-Layer Address Option, TLLA или TLLAO) — отличается от опции "канальный адрес источника" только типом, который равен 2. Благодаря этому, формат обеих опций можно представить на одной иллюстрации; их общий формат показан нарис. 5.12, а формат для Ethernet — на рис. 5.13.
Кроме того, в сообщении NA мы запланировали несколько флагов. Помимо уже знакомых нам флагов S и O, мы отведем бит для флага R, необходимость в котором возникнет ниже, в §5.4.2. Окончательный формат NA будет таким, как показано на рис 5.14(рис 5.14) Объявление соседа — NA
Прекрасно, теперь наш узел IPv6 может составить сообщение NS или NA, когда потребуется. А в какой сетевой интерфейс он отправит это сообщение, если у него их несколько?
Что касается NS, то это сообщение уходит в интерфейс, подключенный к тому каналу, где проводится розыск соседа. Ведь мы с самого начала отметили, что отношение соседства между узлами определено только на заранее заданном канале. Поэтому розыск соседа проводится не глобально, а только после того, как узел-искатель определился с каналом. В свою очередь, соседа разыскивают, когда для него есть исходящий пакет и уже известен выходной интерфейс. Тогда будет вполне резонно передать и сообщение NS в тот интерфейс, на который указала маршрутизация исходящего пакета. Это обеспечит однозначный выбор канала для проведения розыска соседа, поскольку в архитектуре IP к интерфейсу нельзя одновременно подключить больше одного канала.
Что же касается сообщения NA, то в этом случае выбор интерфейса зависит от того, по запросу ли высылается данное сообщение. Если NA — это ответ на запрос NS (S = 1), то его достаточно передать в тот же сетевой интерфейс, откуда пришел запрос, и тогда оно заведомо попадет в нужный контекст канала. Ну, а если NA не по запросу (S = 0), то контекст канала известен из других соображений. Например, если это объявление о смене канального адреса, то его надо передать именно в тот интерфейс, на котором эта смена произошла.
Вот теперь розыск соседей на самом деле возможен: у нас есть форматы сообщений ND и правила работы с ними. А какую еще информацию извлекает из успешного обмена сообщениями NS и NA локальный узел А, кроме канального адреса искомого соседа Б? Очевидно, он узнаёт, что передача данных по каналу от А к Б и обратно, от Б к А, была возможна хотя бы на момент розыска соседа Б. Таким образом, ND позволяет узлу А убедиться в том, что доступность соседа Б симметричная, а NUD поддерживает актуальность этих сведений. В случае же асимметричной доступности, когда канал однонаправленный, ND просигнализирует полную недоступность узла Б. Иными словами, ND поддерживает только двунаправленные каналы [§3.2 RFC 4861].
Возможно, когда-нибудь в будущем состоится попытка распространить ND и на асимметричные каналы [§3.2 RFC 4861]. Произойдет это, конечно же, когда возникнет спрос на такие каналы в среде IPv6.
На этом мы завершили работу над первой частью протокола розыска соседей, посвященной разрешению индивидуальных адресов IPv6 в канальные адреса. Ниже мы встретим еще несколько задач, решение которых будет удобным и логически обоснованным, если его оформить в рамках того же самого протокола. Поэтому протокол розыска соседей IPv6 (Neighbor Discovery Protocol, NDP) не ограничен одним только разрешением адресов [§3 RFC 4861]. Но, как говорится, всему свое время.
Завершим раздел мы тем, что обратим внимание, как полезные разработки IPv6 постепенно находят себе дорогу обратно в
IPv4. Так, в современных версиях
Если птица ходит уткой, плавает уткой и крякает уткой, то ее называют уткой. (Дж. У. Райли)
CROWD: A witch! A witch! A witch! We've got a witch! A witch!
VILLAGER #1: We have found a witch, might we burn her?
CROWD: Burn her! Burn!
BEDEMIR: How do you know she is a witch?
VILLAGER #2: She looks like one.
BEDEMIR: Bring her forward.
WITCH: I'm not a witch. I'm not a witch.
BEDEMIR: But you are dressed as one.
WITCH: They dressed me up like this.
(Monty Python and the Holy Grail)
Вертит, как цыган солнцем. (Украинская поговорка)
До сих пор мы говорили об узлах IPv6 в целом, практически не проводя границ между хостом и маршрутизатором, и это было вполне оправданно, пока мы обсуждали базовые механизмы IPv6, не зависящие от типа узла.
Нам не повредит напомнить себе лишний раз определения хоста и маршрутизатора IP. В идеале, хост только создает и потребляет пакеты, тогда как маршрутизатор занят исключительно тем, что продвигает пакеты хостов. Конечно, на практике маршрутизатор обычно содержит в себе элементы хоста , необходимые для обработки служебных сообщений, удаленного управления и участия в протоколах маршрутизации.
Однако рано или поздно перед нами должен встать вопрос, можем ли мы положиться на наш опыт IPv4 и по-прежнему считать различия в устройстве и поведении хоста и маршрутизатора незначительными во всем, что не касается функции продвижения транзитного трафика.
Ведь, как мы знаем, в IPv4 произошла конвергенция этих двух типов узла, в результате чего между ними не осталось принципиальных отличий, а выбор роли узла свелся к настройке флажка, пропускать ли транзитный трафик. Современные операционные системы готовы обеспечить работу и хоста, и маршрутизатора IPv4, используя один и тот же программный код, а за этим стоит единство алгоритмов и структур сетевого стека. В частности, оба типа узлов IPv4:
пользуются таблицей маршрутов одного и того же вида: "префикс $$\to$$ следующий шаг" [ср. §3.3.1.2 RFC 1122 и §5.2.4.3 RFC 1812];
Хотя [§3.3.1.2 RFC 1122] говорил, что полноценная таблица маршрутов — необязательный элемент в сетевом стеке хоста IPv4, на практике она встречается повсеместно.
Мы сознательно не упоминаем ненумерованные интерфейсы "точка-точка", так как они не требуют розыска соседей.
А на самом глубоком уровне такую конвергенцию обеспечивает определенная модель взаимодействия хостов и маршрутизаторов одной подсети. Согласно этой модели, хост сам принимает решение, кому из соседей передать исходящий пакет, тогда как маршрутизатор только продвигает транзитный трафик, который был передан ему хостом. Чтобы эта модель работала, хост должен обладать полной информацией о логической топологии канала, которому назначена подсеть. Только тогда хост сможет правильно принять решение о том, является ли удаленный узел соседом. Это очень важное решение; ведь если хост ошибется и посчитает соседом узел, который на самом деле доступен только через маршрутизатор, то попытка передать ему пакет будет обречена на неудачу.
Сейчас мы снова опираемся на постулат, который утверждает, что одна подсеть отвечает не более чем одному каналу [§2.1 RFC 4291].
Проанализируем с этой точки зрения тест на соседство IPv4. Допустим, что узел А собирается передать пакет, адресованный узлу Б, посредством одного из своих интерфейсов И, которому назначены адреса 192.0.2.3/28 и 203.0.113.131/28. Следовательно, с точки зрения узла А, каналу , к которому подключен интерфейс И, назначены подсети 192.0.2.0/28 и 203.0.113.128/28. Если адрес назначения пакета Б равен 192.0.2.8 или, скажем, 203.0.113.136, то узел А немедленно определит, что этот адрес принадлежит соседу, потому что он содержит префикс непосредственно подключенной подсети. С точки зрения IP, такой пакет можно передать напрямую его адресату, полагаясь на канальный уровень стека. В то же время, ни один из адресов 192.0.2.150, 198.51.100.1 или 203.0.113.2 не принадлежит соседу, и, следовательно, такие адресаты доступны только косвенно, через маршрутизатор.
На самом деле, тому же каналу могут быть назначены и другие подсети IPv4, о которых узел А не знает, потому что из них ему адрес не назначили.
Эта логика IPv4 основана на гипотезе, что все узлы одной подсети — попарно соседи и могут передавать друг другу пакеты напрямую, не задействуя маршрутизатор. Такое допущение, безусловно, справедливо для широковещательных каналов, например, Ethernet, равно как и для соединений "точка-точка". Но насколько оно универсально? Существуют ли каналы, в которых оно не выполняется?
Оказывается, что в действительности спрос на такие каналы есть. Их принято обозначать термином "каналы множественного доступа без широковещания" или аббревиатурой NBMA (Non-Broadcast Multiple Access). Это не совсем точный термин, потому что на практике в каналах NBMA обычно отсутствует не только широковещание, но и свободная адресация одних узлов другими. Простейший пример такого канала — это топология "звезда" (рис. 5.15), где концевые узлы могут передавать пакеты центральному узлу (концентратору), тогда как напрямую обмениваться данными друг с другом концевые узлы не могут. В топологии "звезда" концентратор — сосед любому концевому узлу, и наоборот, но никакие концевые узлы — не соседи друг другу.
(рис 5.15) Канал NBMA "звезда" и его граф
Классические примеры каналов NBMA — это "звезды" на основе Frame Relay и ATM, где каждый "луч" был виртуальным соединением (virtual circuit).
Современными примерами каналов NBMA типа "звезда" служат кабельные, сотовые и xDSL-сети. В отличие от Ethernet и WiFi, в них концентратор не играет роли коммутатора. Поэтому обмен пакетами между конечными узлами в них возможен только на сетевом уровне, при поддержке маршрутизатора, логика которого часто встроена в сам концентратор.
Но даже локальную сеть Ethernet можно превратить в "звезду" NBMA с помощью частных VLAN. Делают это из соображений безопасности, чтобы централизованно управлять доступом между конечными узлами, не отказываясь при этом от простой схемы "одна ЛВС и одна подсеть".
Если бы мы проводили теоретический анализ топологии канала, то нам, возможно, следовало бы различать схемы "общая среда без широковещания" (когда возможна индивидуальная передача любому узлу, если его канальный адрес заранее известен) и "точка-многоточка" (звезда) в чистом виде [§4.1 RFC 4903]. Однако на практике чаще всего встречается та или иная их комбинация, когда только некоторые пары узлов — соседи между собой. Поэтому для нас сейчас эти две схемы — только частные, крайние случаи более общей модели, в которой возможность узлов канала адресовать друг друга индивидуально и группами ограничена заданным образом. Такую модель вполне допустимо причислить к NBMA.
Конечно, внимательный читатель обоснованно возразит, что такая "звезда" — это вовсе не один канал, а комбинация нескольких каналов "точка-точка", и он будет абсолютно прав, по крайней мере, в теории. Однако, с практической точки зрения, превратить каждый "луч" в отдельный канал — это неудачный выбор. Представьте себе концентратор, к которому уже подключены тысячи абонентов и ежедневно подключаются сотни новых. Если действовать прямолинейно и работать с каждым подключением как с отдельным каналом "точка-точка", то сегодня концентратору потребуется несколько тысяч сетевых интерфейсов, а завтра это число может достичь и миллиона. Хотя теоретик не увидит в этом ничего страшного, на практике число сетевых интерфейсов узла не может расти неограниченно.
Число сетевых интерфейсов узла ограничено не только потому, что программисты-разработчики сетевого стека поленились и использовали неоптимальный, немасштабируемый алгоритм работы с ними. Более фундаментальная проблема в том, что сетевой интерфейс — это центральный объект управления, с которым связана определенная конфигурация практически в каждом компоненте сетевого стека. Впрочем, этот аргумент можно отклонить на том основании, что объем ОЗУ в современных вычислительных системах практически неограничен, а любое число однотипных интерфейсов можно настроить с помощью одного шаблона. Еще один веский аргумент за ограничение числа сетевых интерфейсов заключается в том, что политику сетевой безопасности удобно формулировать в терминах входного и выходного интерфейсов пакета, но тогда число правил такой политики может расти как квадрат числа интерфейсов. Но даже эту трудность можно обойти, объединив интерфейсы в зоны безопасности или перейдя к иной модели управления доступом. Однако в конечном итоге оказывается, что проще ограничить число интерфейсов, нежели постоянно преодолевать препятствия.
Именно поэтому возникает спрос на искусственное решение, когда одна подсеть назначена множеству подключений, а это автоматически превращает их в один канал согласно фундаментальному правилу IP: "Одна подсеть — это один канал" [§2.1 RFC 4291]. Конечно, такая конструкция напоминает цирковой фокус с легким привкусом обмана. Тем не менее, давайте посмотрим, чего будет стоить ее реализация в IPv6. Ведь мы находимся в поле эксперимента, а искомая возможность вполне востребована. Как обычно, в процессе мы встретим и решим более глубокие и общие задачи.
Новые условия таковы, что канал может обладать нетривиальной логической топологией, когда не все его узлы — соседи. Ранее, когда модель канала была простой и требовала соседства между всеми узлами, эта топология канала была скрыта в самом тесте на соседство IPv4. Теперь же информацию о топологии канала придется задавать и хранить явным образом, например, в виде матрицы соседства, где 1 означает пару соседей, а 0 — разъединенную пару узлов. Такие матрицы для канала "звезда" (рис. 5.15) и широковещательного канала с тем же набором узлов приведены в табл. 5.1 и табл. 5.2, соответственно. Придется ли вводить подобную матрицу в настройки каждого из узлов канала?
Интересующие нас матрицы соседства — симметрические, потому что, согласно (нашей модели из §5.1, отношение соседства коммутативно.
| A | Б | В | Г | М | |
|---|---|---|---|---|---|
| А | 1 | 0 | 0 | 0 | 1 |
| Б | 0 | 1 | 0 | 0 | 1 |
| В | 0 | 0 | 1 | 0 | 1 |
| Г | 0 | 0 | 0 | 1 | 1 |
| М | 1 | 1 | 1 | 1 | 1 |
| А | Б | В | Г | М | |
|---|---|---|---|---|---|
| А | 1 | 1 | 1 | 1 | 1 |
| Б | 1 | 1 | 1 | 1 | 1 |
| В | 1 | 1 | 1 | 1 | 1 |
| Г | 1 | 1 | 1 | 1 | 1 |
| М | 1 | 1 | 1 | 1 | 1 |
Для начала мы ограничимся каналом, который обслуживает один маршрутизатор. Остальные узлы такого канала — хосты. Если бы топология этого канала была совершенно произвольной, то, наверное, обойтись без дублирования топологической информации было бы нельзя, и понадобился бы какой-то протокол ее распространения.
Однако для IP практическую ценность представляют только топологии, в которых маршрутизатор — сосед каждого хоста. Действительно, в противном случае для беспрепятственной работы канала потребовался бы транзитный трафик через хосты, а это по определению невозможно. Говоря языком элементарной теории графов, граф "звезда" с маршрутизатором в центре будет подграфом любой топологии канала, представляющей интерес в контексте TCP/IP. Это правило иллюстрирует рис 5.16(рис 5.16) Канал NBMA "бабочка" содержит подграф "звезда"
Тогда маршрутизатор IPv6 будет естественным хранителем информации о топологии канала, потому что он может обслуживать практически неограниченное число хостов (до $$\sim 2^{63}$$) и при этом он заведомо способен передать сведения о топологии канала каждому подключенному хосту. Механизм распространения этих сведений нам сейчас предстоит разработать.
Каких начальных настроек будет достаточно хосту IPv6 с одним сетевым интерфейсом, чтобы начать работу с сетью в таких условиях? Нетрудно убедится, что абсолютный минимум — это его собственный адрес и адрес маршрутизатора. В то же время, длина префикса подсети здесь излишня, так как она больше не содержит информации о топологии канала.
Здесь мы имеем в виду адрес хоста, область которого больше, чем внутриканальная. Помимо него, у хоста будет как минимум один внутриканальный адрес — к этому требованию мы уже пришли в §2.5.
В то же время, адрес маршрутизатора вполне может быть внутриканальным. Мы обсудили это там же, в §2.5.
Конечно же, как это было сказано в §2.6, адрес IPv6 назначают не всему узлу в целом, а определенному его интерфейсу — в данном случае, интерфейсу в интересующий нас канал. По текущим условиям, такой интерфейс у каждого узла один, и это позволяет нам для простоты говорит "адрес узла".
В отсутствие других сведений хосту IPv6 не остается ничего, кроме как передавать (весь исходящий трафик через этот маршрутизатор, так что для хоста это будет (маршрутизатор по умолчанию (default router).
Очевидное исключение — это пакеты, в которых хост адресует сам себя. Их не следует отправлять на маршрутизатор по умолчанию, потому что такие пакеты вообще не покидают границ хоста.
Любой имеющий опыт работы с IPv4 сказал бы, что у нашего модельного хоста есть только маршрут по умолчанию, (::/0, и нет маршрута на непосредственно подключенную подсеть. Однако суть как раз в том, что модель хоста IPv6 целенаправленно избегает появления таблицы маршрутов среди структур данных хоста. Сделано это, не в последнюю очередь, для упрощения минимальной реализации, которая найдет себе место во встроенных системах. Фактически, происходит попытка снять с хоста все функции, которые будут заведомо доступны в маршрутизаторе. Реализация хоста IPv6 может включать в себя эти функции факультативно, но они не обязательны, и их отсутствие не повредит работоспособности хоста.
Чтобы начать передачу данных через маршрутизатор, хосту надо выяснить его канальный адрес, а для этого, в зависимости от типа канала, может потребоваться розыск соседа. Однако успех этой операции обеспечен, несмотря на сложную топологию канала, тем, что маршрутизатор — заведомо сосед хоста, если канал отвечает базовым требованиям TCP/IP. Так оказывается, что для начала работы хосту IPv6 вообще не нужны дополнительные сведения о топологии канала: ему достаточно слать весь исходящий трафик через маршрутизатор по умолчанию, и он уже способен это сделать.
(рис 5.17) Маршрутизация обратно в канал NBMA и за его пределы
Для топологии "звезда" этого решения будет вполне достаточно, так как у хостов все равно нет иного способа обмениваться данными, кроме как через маршрутизатор, хотя формально они подключены к одному каналу. Такая конфигурация представлена на рис. 5.17.
Теперь усложним топологию канала и допустим, что она включает в себя прямые связи между некоторыми хостами. Для примера рассмотрим топологию "бабочка" (рис. 5.16), где две пары хостов снабжены такими связями. Ее матрица соседства приведена в 5.3. Как мы уже отметили, "звезда" обязательно будет подграфом такой топологии (см. рис. 5.16), и наша простейшая схема работы хоста, с одной стороны, останется возможной. С другой стороны, она утратит свою оптимальность, так как хосты А и Б или В и Г смогли бы обмениваться пакетами напрямую, не создавая нагрузки на маршрутизатор М. Сведения о соседстве хостов А и Б, а также В и Г, хранятся маршрутизатором М. Каким образом М мог бы сообщить, скажем, хосту А, что тот вполне способен передавать пакеты хосту Б напрямую? И когда М следует это сделать? Потребуется ли особый протокол, по которому А запросит недостающую информацию у М?
| А | Б | В | Г | М | |
|---|---|---|---|---|---|
| А | - | 1 | 0 | 0 | 1 |
| Б | 1 | - | 0 | 0 | 1 |
| В | 0 | 0 | - | 1 | 1 |
| Г | 0 | 0 | 1 | - | 1 |
| М | 1 | 1 | 1 | 1 | - |
На самом деле, здесь достаточно простой (переадресовки (redirect), которую будет естественно оформить в виде сообщения ICMPv6, принадлежащего к семейству ND [§4.5 RFC 4861]. Последовательность событий может быть такой:
затем, чтобы оптимизировать будущий трафик, М направляет хосту А сообщение ND типа "переадресовка", которое уведомляет, что адресат Б доступен через следующий шаг Б, то есть напрямую;
хост А принимает переадресовку к сведению, исправляя запись Б в кэше адресатов на "Б $$\to$$Б";
Конечно, маршрутизатор М не должен слать переадресовку на (каждый пакет от А к Б, если хост А игнорирует переадресовки и продолжает слать пакеты через М. Из соображений устойчивости и безопасности частоту переадресовок следует ограничивать [§8.2 RFC 4861]. Впрочем, это справедливо практически для всех сообщений ICMPv6.
У этого решения есть несколько очевидных преимуществ. Во-первых, оно использует уже готовые структуры данных, в частности, кэш адресатов хоста. Во-вторых, оно требует всего лишь одного нового сообщения ND с простыми свойствами. В-третьих, оно устойчиво к потере и дублированию переадресовок. Наконец, в-четвертых, матрица соседства, хранимая маршрутизатором М, может изменяться по ходу работы канала, и хост А оперативно обновит свой взгляд на топологию канала без внешнего вмешательства благодаря работе кэша адресатов. Например, если связь А–Б исчезнет, то хост А станет слать трафик в адрес Б через маршрутизатор М, как только устареет прежняя запись в кэше адресатов DC.
Своевременно обнаружить исчезновение связи А–Б и переключиться обратно на маршрутизатор М хосту А помогут механизм (NUD (§5.1) и обратная связь между записями NC и DC. Так, когда NUD сигнализирует недоступность соседа Б, можно удалить заодно и все записи DC, чей следующий шаг равен Б. Тогда время жизни самих записей DC можно и не ограничивать. Мы обобщим эту идею ближе к концу раздела.
Данный механизм настолько прост и универсален, что он заслуживает роли стандартного для всех хостов IPv6. Кроме того, он предъявляет весьма низкие требования к вычислительным возможностям хоста, а это как нельзя лучше соответствует тенденции применять IP в промышленных сетях управления, где хосты — устройства маломощные и с низким энергопотреблением.
Таким образом, хост IPv6 при передаче исходящих пакетов руководствуется существенно иными правилами, чем хост IPv4. Это различие нам следует четко отразить в терминологии и процедурах, чтобы избежать путаницы.
Независимо от версии IP, перед хостом стоит одна и та же основная задача:
(Дано: индивидуальный адрес назначения исходящего пакета IP.
(Вопрос: доступен ли сетевой интерфейс с этим адресом непосредственно, на канальном уровне?
Что означает для хоста H доступность удаленного сетевого интерфейса I на канальном уровне? Очевидно, это значит, что хост H посредством одного из его собственных, локальных интерфейсов J может обмениваться данными канального уровня с интерфейсом I, а значит, и его узлом-владельцем. Ведь всякий сетевой интерфейс а) подключен ровно к одному каналу и б) принадлежит ровно одному узлу. (Когда интерфейс не подключен, он не готов к работе и, с нашей точки зрения, не существует.) Необходимое, но недостаточное условие доступности на канальном уровне — это чтобы у хоста H нашелся интерфейс J, подключенный к тому же каналу L, что и интерфейс I. Дополнительное условие — это фактическая возможность обмена данными на канальном уровне между интерфейсами I и J по каналу L. Если, например, в локальных сетях второе следует из первого, то в каналах NBMA все зависит от логической топологии данного канала.
Будет любопытно отметить, что понятие "петли" (loopback) позволяет обобщить эту конструкцию и на случай, когда интерфейс назначения I принадлежит локальному хосту H. В этом случае тривиальное решение выглядит как J = I. Впрочем, если у хоста несколько интерфейсов, подключенных к каналу L, то возможны и нетривиальные решения, где J $$\ne$$ I.
Если ответ на этот ключевой вопрос утвердительный, то хост проведет разрешение сетевого адреса назначения в отвечающий ему канальный адрес с помощью ARP или ND, в зависимости от версии IP, и сможет передать пакет его адресату напрямую, из интерфейса в интерфейс. В противном случае хост должен воспользоваться услугами маршрутизатора.
А вот общепринятые способы найти ответ на данный вопрос в двух версиях IP существенно разнятся.
Как мы уже сказали, хост IPv4 исходил из предположения, что все адреса в одной с ним подсети доступны напрямую. О таких адресах мы говорили, что они непосредственно подключенные (directly connected). У непосредственно подключенного адреса может быть всего два состояния: или он вообще не назначен, или он принадлежит интерфейсу, который доступен локальному хосту на канальном уровне. Все прочие адреса не являются непосредственно подключенными и доступны хосту только через маршрутизатор. Классификацию адресов хост IPv4 проводил с помощью несложной побитовой арифметики, используя список префиксов подсетей, назначенных его собственным интерфейсам. Вдобавок, если результат теста был положительный, то наиболее точный префикс однозначно указывал на интерфейс, сквозь который доступен непосредственно подключенный адрес.
Напротив, хост IPv6 больше не доверяет информации о подсетях, потому что она не всегда отражает действительную топологию каналов. Хост IPv6 не может априори определить, принадлежит ли произвольный адрес соседу, и потому рассчитывает на дополнительные источники информации, чтобы классифицировать каждый адрес назначения индивидуально. (Если об адресе нет иной информации, то хост IPv6 по умолчанию полагает, что соответствующий сетевой интерфейс недоступен на канальном уровне, а адрес находится "(вне канала" (off-link) [§3 RFC 5942].
Обратите внимание на это правило. Оно существенно отличается от общепринятой практики IPv4.
Из каких источников хост может почерпнуть иные сведения? Во-первых, адреса его собственных интерфейсов доступны ему напрямую, потому что хост всегда может послать пакет сам себе, минуя маршрутизатор. Во-вторых, если в настройках хоста указан адрес маршрутизатора по умолчанию, то он также заведомо доступен напрямую. В свою очередь, эта настройка открывает хосту возможность обмена данными с другими хостами, даже если их адреса "вне канала". В процессе этого обмена хост может получить от маршрутизатора переадресовку вида "Б$$\to$$Б" и таким образом узнать, что интерфейс с адресом Б доступен ему на канальном уровне. Все эти источники информации сообщают хосту IPv6, что адрес находится "(на канале" (on-link).
Модель, в которой все адреса по умолчанию находятся "вне канала", довольно нова и опыт работы с ней относительно невелик, так что даже некоторые стандарты потребовали корректив. Скажем, все еще действующий стандарт ND предписывал, что адрес — "на канале", если о нем пришло объявление NA, или же если он выступает источником любого сообщения ND [§2.1 RFC 4861]. Однако это поведение противоречит модели, в которой единственный достоверный источник такой информации — маршрутизатор. Кроме того, оно открывает возможность для атаки, когда злоумышленник на канале перехватывает трафик, предназначенный узлу вне канала. Поэтому [п.3 §3 RFC 5942] запрещает хосту подобный трюк.
В то же время, хосту не возбраняется "разогревать" свой кэш соседей NC, заранее создавая в нем записи, но не делая при этом выводов о непосредственной доступности узлов. К примеру, получив запрос NS с опцией SLLA, хост создает о его источнике запись NC в состоянии (ПРОСРОЧЕННАЯ [§3 RFC 5942 и §7.2.3 RFC 4861], потому что в будущем это позволит (сэкономить время и обойтись без явного розыска соседа, как мы обсудили в §5.1. Но воспользоваться этой записью NC для прямой передачи хост будет вправе, только когда маршрутизатор подтвердит, что адресат "на канале", и будет создана соответствующая запись в кэше адресатов DC.
Только теперь, поняв и сформулировав главное различие между алгоритмами теста на соседство IPv4 и IPv6, мы можем безопасно оптимизировать алгоритм IPv6, не рискуя навредить нашему пониманию основ.
На практике о (некоторых диапазонах адресов может быть известно, что они "на канале", с точки зрения данного хоста. К их числу относятся:
Кроме того, условно считается, что групповые адреса всегда "на канале", поскольку групповой трафик IP распространяется в сети не так, как индивидуальный. Групповые маршрутизаторы слушают все обслуживаемые ими группы и потому, с точки зрения источника трафика, они ничем не отличаются от рядовых членов группы на том же канале. Подробнее мы поговорим об этом в §6.4. Кроме того, к групповым адресам никогда не применяют ND. Поэтому групповые адреса стоят особняком, и нет смысла пытаться включить их в наш текущий дискурс о критериях "на канале/вне канала".
В первых двух случаях диапазоны адресов уже представлены префиксами. Третий случай не столь важен, но в нем диапазон тоже можно представить набором префиксов. Поэтому вместо произвольных диапазонов мы можем говорить о префиксах "на канале". Если список этих префиксов сообщить хосту, то он сможет эффективнее использовать свое соседство с другими узлами и реже обращаться к услугам маршрутизатора по умолчанию. Соответствующая структура в памяти хоста IPv6 так и называется: (список префиксов (Prefix List). Если адрес назначения содержит в себе один из этих префиксов, то он для данного хоста — "на канале".
Сделаем замечание по терминологии. Строго говоря, префикс может (содержаться в адресе, тогда как адрес может (принадлежать блоку, представленному этим префиксом. Поэтому говорить, что "адрес принадлежит префиксу", как советовал нам редактор, было бы искажением технической сути этих отношений.
Свежеполученный опыт подсказывает нам, что список префиксов надо заполнять с осторожностью, чтобы не навредить работе каналов нетривиальной топологии. По умолчанию в этом списке ровно один элемент — внутриканальный префикс FE80::/10 [§5.1 RFC 4861, §3 RFC 5942].
По этой причине хосты на канале NBMA не могут свободно взаимодействовать друг с другом, используя внутриканальные адреса. На это ограничение приходится идти ввиду особой роли внутриканальных адресов. Хотя, зонная архитектура IPv6 вполне допускает маршрутизацию внутриканальных адресов маршрутизатором обратно в тот же канал, использовать эту возможность по умолчанию не следует.
В то же время, скажем, интерфейс, подключенный к каналу Ethernet, еще ничего не говорит о фактической топологии этого канала. Например, с помощью частных VLAN топологию такого канала можно ограничить и превратить его из широковещательного в NBMA. По этой причине хосту не следует автоматически вносить в список префиксов подсети только потому, что они назначены интерфейсам определенного типа.
Согласно нашему генеральному плану, точными сведениями о топологии канала обладает маршрутизатор. Поэтому самая достоверная информация о списке префиксов могла бы исходить именно от маршрутизатора. Пока что мы не располагаем подходящим механизмом для этого, но скоро он у нас тоже появится (в §5.4.2).
С другой стороны, теперь подсеть, в которой находится интерфейс хоста IPv6, больше не влияет на классификацию "на канале" или "вне канала", как
это было в IPv4. Поэтому мы вполне можем допустить случай, когда удаленный адрес оказывается "на канале", хотя не принадлежит подсети хоста. Для
примера рассмотрим сценарий на рис 5.18(рис 5.18) Один канал и один маршрутизатор, но две подсети
Как работала бы подобная схема в IPv4? Допустим, у хоста А был бы адрес (192.0.2.33/24, у хоста Б - (203.0.113.44/24, а у маршрутизатора М — (192.0.2.1/24 и (203.0.113.1/24. В этой схеме весь трафик между хостами А и Б неизбежно проходил бы через маршрутизатор М. Почему? Во-первых, если бы хосту А пришла переадресовка ICMP вида "(203.0.113.44 $$\to$$ (203.0.113.44", то хост А проигнорировал бы ее, потому что следующий шаг в ней не являлся непосредственно подключенным [§3.2.2.2 RFC 1122]: (203.0.113.44 не принадлежал подсети (192.0.2.0/24. А во-вторых, маршрутизатор М не стал бы слать хосту А такую переадресовку [§5.2.7.2 RFC 1812], потому что он заранее знал: она будет проигнорирована. Те же соображения, с точностью до перестановки адресов, определили бы и обратный ход трафика от Б к А.
Теперь же, в IPv6, у хоста А не будет причин не принять переадресовку "Б $$\to$$Б" просто потому, что именно маршрутизатор —
достоверный источник сведений о топологии канала [§8.1 RFC 4861]. В свою очередь, маршрутизатор М не должен ограничивать переадресовки одной
подсетью, если он располагает информацией о том, что хосты А и Б — соседи. И тогда хост А сможет узнать и принять к сведению, что хост Б "на
канале" и доступен напрямую, как это показано на рис 5.19(рис 5.19) Механизм переадресовки
Обратите особое внимание на это важное различие между переадресовками IPv4 и IPv6 [§3.1 RFC 4861].
Это новое свойство нам следует перенести и на список префиксов. Для этого скажем: элементы списка префиксов не обязаны принадлежать подсетям, в которых находится сетевой интерфейс хоста. К примеру, список префиксов хоста Х с адресом (2001:DB8:33::33/64 вполне может содержать префиксы (2001:DB8:33::/48 (префикс короче подсети) и (2001:DB8:777::/64 (другая подсеть). Все адреса с такими префиксами будут для хоста Х "на канале", и тот сможет слать пакеты их владельцам напрямую.
Теперь нам пора поработать над форматом переадресовки. Как мы уже сказали, она будет разновидностью сообщения ICMPv6 из семейства ND [§4.5 RFC 4861]. Переадресовка — это сообщение с любопытными свойствами. С одной стороны, переадресовка — это полезная подсказка хосту, а вовсе не извещение об ошибке. Поэтому ее численный тип находится в диапазоне справочных сообщений 128–255 и равен 137. С другой стороны, переадресовка высылается в ответ на произвольный пакет IPv6, что отличает ее от других справочных сообщений и роднит с извещениями об ошибках. В этом случае, как мы знаем, служебное сообщение должно содержать в себе начало пакета, который его вызвал, чтобы получатель смог более точно отреагировать на него, например, определив, к какому сеансу TCP относится это сообщение.
Разрешить это противоречие в свойствах переадресовки будет довольно просто, если мы воспользуемся механизмом опций ND и поместим пакет-виновник (или его начало) в специально отведенную для этой роли опцию "переадресованный заголовок" (Redirected Header), показанную на рис. 5.20 Правила работы с ней очевидны:
(рис 5.20) Опция ND "переадресованный заголовок"
А какие обязательные поля мы поместим в тело переадресовки? Назначение переадресовки заключается в том, чтобы сообщить получателю, что оптимальный путь к адресату Б пролегает через следующий шаг Ш, где Б и Ш — адреса IPv6. Поэтому фиксированная часть тела переадресовки состоит, помимо выравнивания, всего из двух адресов IPv6. Как показано на рис. 5.21 первым из них расположен адрес следующего шага Ш. Так как его владелец — заведомо сосед получателя, то, по терминологии ND, это адрес цели (target address). Непосредственно за ним идет адрес назначения Б.
(рис 5.21) Переадресовка IPv6
Адрес цели идет первым для единообразия с другими сообщениями ND.
Хотя адрес назначения также содержится в копии пакета-виновника (опция "переадресованный заголовок"), его дублирование в фиксированной части переадресовки облегчает работу ее получателя.
Здесь мы видим еще одно важное отличие между переадресовками IPv6 и IPv4. Как мы помним, переадресовка IPv4 могла быть для одного адреса или целой сети, хотя формат переадресовки для сети был классовым и не имел смысла в CIDR. Переадресовка же IPv6 — исключительно для одного адреса, и это позволяет хранить ее сведения в кэше DC.
Адреса Б и Ш вполне могут совпадать. Именно этот случай нам сейчас особенно интересен. Ведь такой переадресовкой маршрутизатор сообщает, что узел Б, он же Ш, находится "на канале".
Случай, когда адреса Б и Ш не совпадают, тоже, безусловно, важен на практике. Такая переадресовка говорит хосту, что оптимальный путь к адресату Б пролегает через другой маршрутизатор на том же канале, а именно Ш.
Прочие требования к годной переадресовке вполне предсказуемы [§8.1 и §8.2 RFC 4861]:
Эта проверка также защищает от переадресовок, которые задержались в сети. Например, если хост А был переадресован маршрутизатором М1 к маршрутизатору М2, а тем, с свою очередь, к маршрутизатору М3, то запоздавшая копия переадресовки М1 будет хостом А проигнорирована.
А как быть хосту, если от его текущего маршрутизатора по умолчанию пришла переадресовка вида "Б $$\to$$Ш", но в кэше DC еще нет записи об адресате Б? Хотя стандарт признает такую переадресовку законной [§8.1 RFC 4861] и рекомендует создать новую запись "Б$$\to$$Ш" [§8.3 RFC 4861], подобная переадресовка подозрительна, потому что она содержит сведения об адресате, которому хост в обозримом прошлом никаких пакетов не слал. Разобраться в этой ситуации хосту поможет копия пакета-виновника в теле переадресовки.
пошаговая адресация маршрутизаторов происходит по их внутриканальным адресам [§8 RFC 4861], и поэтому:
oв заголовке IPv6 адрес источника должен быть внутриканальным, так как переадресовка исходит от маршрутизатора данного канала;
oв теле переадресовки адрес цели Ш должен или совпадать с адресом назначения Б (переадресовка на хост), или быть внутриканальным (переадресовка на другой маршрутизатор).
Под пошаговой адресацией мы имели в виду обращение к узлу, которое происходит, когда другой сосед использует его в качестве следующего шага пакета. Архитектура IPv6 самым настоятельным образом содействует тому, чтобы хост в этом случае обращался к маршрутизатору по его внутриканальному адресу.
Та же тенденция прослеживается и во взаимодействии между маршрутизаторами IPv6. Так, отношения смежности между маршрутизаторами IS IS [§3 RFC 5308] и OSPF [§2.5 RFC 5340] устанавливаются именно по их внутриканальным адресам.
Пока хосты IPv6 обращаются к маршрутизаторам по внутриканальным адресам, хостам не придется делать исключения из правил "на канале" — "вне канала". Ведь внутриканальный префикс FE80::/10 автоматически попадает в список префиксов "на канале". В противном же случае хост должен был бы постоянно "помнить", что адрес маршрутизатора по умолчанию находится "на канале". Это несложно, но немного нарушает стройность алгоритма передачи пакетов хостом.
Полученный формат переадресовки уже позволяет сообщить сам факт, что адрес назначения — "на канале". Но всегда ли этого достаточно? Допустим, хост А послал пакет адресату Б через маршрутизатор М, получил от маршрутизатора переадресовку вида "Б $$\to$$Б" и сохранил это соответствие в своем кэше адресатов DC. Каковы его дальнейшие действия? Когда придет время послать следующий пакет в адрес Б, кэш адресатов DC укажет хосту А, что адрес Б — "на канале" и принадлежит соседу. То есть придет время для розыска соседа Б с помощью протокола ND. В свою очередь, протокол ND подразумевает, что сосед Б может получать групповые пакеты, рассылаемые хостом А.
Но что будет, если канал между А и Б — NBMA без группового вещания между хостами? Тогда на канальном уровне хост А мог бы передавать соседу Б индивидуальные кадры, если бы знал его канальный адрес. В то же время, групповой запрос NS не дойдет до Б. В этом случае розыск соседа закончится неудачей и обмен данными между А и Б окажется невозможным.
Чтобы обойти и эту особенность каналов NBMA, нам достаточно довести до логического завершения новую функцию маршрутизатора IPv6. Как мы уже отмечали, маршрутизатор IPv6 — это координатор передачи данных по каналу между хостами. Тогда именно он и должен сообщить хосту А недостающие сведения. Для этого достаточно дополнить переадресовку уже доступной нам опцией ND "канальный адрес цели" (§5.1) и поместить в нее канальный адрес узла Б.
Эта опция в переадресовке не помешает и при передаче по широковещательному каналу, так как хосту А не придется самостоятельно разыскивать соседа Б. Тем не менее, для широковещательных каналов это просто оптимизация, тогда как для каналов NBMA это важный механизм, который позволяет IPv6 работать с такими каналами без их искусственной адаптации к широковещательной семантике.
Что хост А сделает с информацией из этой опции? Очевидно, он поместит ее в свой кэш соседей NC. Так как информация исходит от маршрутизатора и еще не подтверждает обоюдной доступности А и Б, подходящим состоянием для записи NC будет ПРОСРОЧЕННАЯ. Этой записью уже можно воспользоваться для непосредственной передачи пакета. Когда хост Б откликнется на этот пакет, то по сигналу вышестоящего протокола запись NC станет ДОСТУПНОЙ. Если же вышестоящий протокол — строго однонаправленный, то хосту Б все равно придется ответить на индивидуальный запрос NS с опцией "канальный адрес источника" (§5.1), который пошлет хост А, когда запись NC перейдет по тайм-ауту в состояние ИСПЫТАНИЕ.
Обратите внимание: хост Б будет обязан пройти тут же самую процедуру переадресовки, чтобы получить право слать прикладные пакета напрямую хосту А, полагая того соседом "на канале".
Последний вопрос о работе этого механизма состоит в том, как маршрутизатор М узнает канальный адрес узла Б в отсутствие полноценного группового
вещания. Конечно, последнее средство, пригодное для всех разновидностей NBMA — это ручная настойка кэша соседей М. Однако М сможет найти
канальный адрес Б автоматически, если канал NBMA эмулирует групповое вещание по принципу веера, известное как "псевдошироковещание"
(pseudobroadcast
До сих пор мы сознательно ограничивались сценарием, когда канал обслуживает один маршрутизатор, а у каждого хоста один сетевой интерфейс, помимо "петли". Насколько трудно будет снять эти ограничения? Начнем с первого из них и рассмотрим канал с несколькими маршрутизаторами. Чтобы подключенный к тому же каналу хост смог пользоваться их услугами, ему, прежде всего, понадобится список их адресов вместо одного адреса маршрутизатора по умолчанию. Так возникает новая структура данных в памяти хоста: список маршрутизаторов по умолчанию (Default Router List). Сейчас мы допустим, что этот список указан вручную в начальных настройках хоста, а над его автоматическим заполнением мы поработаем чуть позже, в §5.4.2. Мы не станем усложнять нашу конструкцию системой приоритетов и положим, что все маршрутизаторы в списке эквивалентны.
Не имея оснований предпочесть какой-то конкретный маршрутизатор из списка, хост может начать работу, просто выбрав запись случайным образом. Этого уже будет достаточно, скажем, чтобы распределить нагрузку густозаселенного канала на несколько маршрутизаторов. Кроме того, дублирование маршрутизаторов повысит отказоустойчивость сети, если каждый хост сможет самостоятельно обнаружить, что его текущий маршрутизатор "пропал без вести", и выбрать из списка новый, "живой" маршрутизатор. Придется ли нам изобрести для этого новый протокол? К счастью, нет. Ведь в нашем распоряжении уже есть замечательный механизм NUD, для работы которого достаточно протокола ND — см. §5.1. Когда NUD сигнализирует о недоступности текущего маршрутизатора, хост может проверить работоспособность следующего по списку, и так далее. Совсем удалять недоступный маршрутизатор из списка, конечно же, не следует. Ведь вполне может оказаться, что к тому времени, когда последний маршрутизатор в списке засбоит, первый уже вернется в строй. На этом задача о нескольких маршрутизаторах решена.
Конечно, поиск "живого" маршрутизатора открыт для оптимизации. К примеру, хост может активно следить за всеми маршрутизаторами в списке с помощью все того же механизма NUD и ранжировать их по состоянию записи в кэше соседей NC: ДОСТУПНАЯ запись лучше ПРОСРОЧЕННОЙ, а та предпочтительнее НЕПОЛНОЙ.
Справедливости ради заметим, что §3.3.1.2 RFC 1122 требовал от хоста IPv4 обязательной поддержки нескольких маршрутизаторов по умолчанию. В то же время, §3.3.1.4 того же RFC 1122 признавал, что в IPv4 нет надежного и общепринятого способа узнать о недоступности текущего маршрутизатора. Этот конфликт между желаемым и действительным неизбежно сказался на практике IPv4, где список маршрутизаторов по умолчанию можно встретить редко.
Сможем ли мы так же легко и изящно снять второе ограничение и обеспечить эффективную работу хоста IPv6, подключенного к нескольким каналам? Для начала мы вновь обратим внимание на уже известный нам факт: отношение соседства определено только в пределах заданного канала. Иными словами, узел Б не может быть соседом узла А "вообще" — они будут соседями только на вполне определенном канале К. В свою очередь, окном в канал для хоста выступает сетевой интерфейс, а структуры данных хоста, которые управляют передачей пакетов в нашей модели, самым непосредственным образом работают с отношением соседства:
Поэтому все эти структуры привязаны к определенному сетевому интерфейсу и не имеют смысла для других интерфейсов того же хоста
К примеру, "продвинутый" хост с несколькими подключениями может участвовать в протоколе маршрутизации и самостоятельно вычислять оптимальные направления передачи пакетов вместо того чтобы надеяться на маршрутизаторы по умолчанию.
На первый взгляд, есть случаи, когда и наш эталонный хост может сам выбрать оптимальный выходной интерфейс. Например, вот типичное заблуждение: "Если адресат исходящего пакета — "на канале" на одном из интерфейсов, то надо выбрать именно этот интерфейс". Но как быть, если этот канал засбоил и в данный момент адресат доступен только через маршрутизатор другого канала? Дело в том, что множественное подключение применяют для решения довольно сложных задач, таких как отказоустойчивость высокой категории, и простые решения здесь не срабатывают. В то же время базовые механизмы хоста IPv6 должны оставаться простыми, чтобы они были по силам не только серверу, но и кофеварке.
Мы же предположим, что у эталонного хоста IPv6 в каждый момент времени есть один выходной интерфейс по умолчанию, который и используется для передачи пакетов.
Теперь нам пора свести воедино наши соображения о том, каков принципиальный алгоритм передачи пакета эталонным хостом IPv6 [§5.2 RFC 4861]. Вызов этого алгоритма будет происходить на каждый исходящий пакет. Входными данными алгоритма послужат сетевой адрес назначения пакета и требуемый выходной интерфейс, а вернуть он должен канальный адрес следующего шага, или же ошибку, если следующий шаг найти не удалось.
Первое решение хоста состоит в том, является ли адрес назначения индивидуальным или групповым. Определить это просто благодаря тому, что все групповые адреса имеют префикс FF00::/8. Дальнейшая работа над групповым адресом для нас сейчас интереса не представляет, так как, согласно нашему плану, групповые адреса IPv6 всегда "на канале", а канальный групповой адрес просто вычисляется из сетевого группового адреса по правилам инкапсуляции IPv6 для каналов данного типа. Например, в Ethernet это уже знакомая нам элементарная операция: взять 32 младших бита группового адреса IPv6 и присоединить к ним префикс 33 33. Поэтому сосредоточим наше внимание на индивидуальных адресах назначения.
Далее хосту надо найти сетевой адрес следующего шага. Здесь хост пытается сэкономить время, применяя кэш адресатов DC, привязанный к выходному интерфейсу. Если адрес назначения уже содержится в левой части записи DC, то правая часть этой записи сразу же отвечает на два вопроса: явным образом она сообщает сетевой адрес следующего шага, а неявно говорит, "на канале" ли адресат. Впрочем, хосту уже неважен ответ на второй вопрос, потому что он знает ответ на первый.
Если же записи об адресе назначения в кэше DC нет, хост должен определить, "на канале" ли адресат. Здесь безопаснее ошибочный диагноз: "Вне канала", — потому что в этом случае пакет уйдет через маршрутизатор. Напротив, ошибочный диагноз: "На канале", — приведет к невозможности передать пакет. Поэтому хост сопоставляет адрес назначения со списком префиксов выходного интерфейса и делает вывод: "На канале", — только если адрес назначения содержит хотя бы один из этих префиксов. В противном же случае хост обязан действовать так, как если бы адресат был "вне канала".
Действующий стандарт ND предписывает поиск длиннейшего префикса [§5.2 RFC 4861], хотя эта информация здесь явно избыточна. Ведь даже если список префиксов содержит более короткий префикс наряду с содержащим его префиксом большей длины, результат проверки не зависит от того, какой именно префикс был обнаружен в адресе назначения. Допустим, например, что список префиксов состоит всего из двух пунктов: 2001:DB8::/48 и 2001:DB8::/64. Тогда адрес 2001:DB8::1234 будет "на канале" вне зависимости от того, какой из префиксов "сработал" при проверке. Информация о длиннейшем префиксе могла бы понадобиться, если бы надо было распространить поиск на несколько сетевых интерфейсов и выбрать интерфейс с самым точным префиксом. Однако выбор выходного сетевого интерфейса сознательно не включен в современную версию ND. По-видимому, поиск длиннейшего префикса в стандарте ND остался с тех пор, когда поддержка нескольких интерфейсов на уровне ND была предметом исследования. Кроме того, поиск самого точного префикса может быть нужен, если хост ведет статистику для каждого префикса на канале: счетчик числа обращений и т.п.
Если по итогам этой проверки адрес назначения находится "на канале", то сетевой адрес следующего шага совпадает с адресом назначения. В противном случае хост выбирает кандидата из списка маршрутизаторов по умолчанию, например, руководствуясь сведениями NUD. Окончательный результат помещается в кэш DC.
Если сведений NUD ни об одном маршрутизаторе нет, то достаточно перебирать список маршрутизаторов по кругу [§6.3.6 RFC 4861]. Тогда довольно скоро в кэше NC появится актуальная информация о доступности хотя бы некоторых маршрутизаторов.
В этом и состоит первая часть эталонного алгоритма передачи пакета хостом IPv6: выбор следующего шага (маршрутизация). Давайте представим его в виде блок-схемы рис 5.22(рис 5.22) Алгоритм маршрутизации выходного пакета эталонным хостом IPv6
Теперь сетевой адрес следующего шага выбран, и слово за модулем ND, чьей задачей будет выяснить отвечающий ему канальный адрес. Процесс ND тоже старается сэкономить время и силы, на этот раз с помощью кэша соседей NC при выходном интерфейсе. Если данный адрес уже закэширован и запись о нем полная, то есть содержит канальный адрес, то достаточно вернуть последний. Конечно, сам факт обращения к записи NC может включить фоновый механизм ее освежения согласно NUD, например, если запись ПРОСРОЧЕННАЯ. Тем временем алгоритм передачи IPv6 успешно завершает работу, так как любой полной записью NC можно немедленно пользоваться для передачи пакета.
И, наконец, если полной записи о следующем шаге в кэше NC еще нет, то хосту остается только поместить пакет в буфер и начать процедуру розыска соседа. В результате ее работы возникнет полная запись NC, если искомый узел доступен, конечно. Так задача сведется к предыдущей, когда запись NC уже есть.
Какими любопытными свойствами обладает этот алгоритм? С одной стороны, он вполне способен распределить нагрузку на несколько маршрутизаторов [RFC 4311], но при этом он не страдает от недостатков случайного распределения, потому что кэширует следующий шаг для каждого адресата.
Проблемы, которые влечет за собой бессистемное распределение пакетов по разным путям, мы уже упомянули в §5.1.
С другой стороны, этот алгоритм не обременен мгновенной проверкой доступности следующего шага. Допустим, хост до некоторых пор пользовался одним маршрутизатором, а затем его маршрутизатор-фаворит неожиданно взяли и выключили. Тогда алгоритм передачи хоста продолжит возвращать канальный адрес выключенного маршрутизатора до тех пор, пока фоновый механизм NUD не сигнализирует о его недоступности и пока не исчезнут указывающие на него записи кэша DC. И только после этого произойдет полное переключение на новый маршрутизатор.
Конечно, это компромисс между надежностью и производительностью: маршрутизаторы не исчезают ежесекундно, тогда как проверять доступность маршрутизатора перед каждым пакетом было бы накладно. К тому же, благодаря непрерывной обратной связи с вышестоящими протоколами, механизм NUD может установить свои тайм-ауты весьма короткими и обнаружить пропажу соседа в считанные секунды после того, как прекратятся сигналы вышестоящих протоколов о его доступности.
Дальнейшая оптимизация состоит в том, чтобы удалить все записи кэша DC, которые ссылаются на пропавшего соседа, как только механизм NUD обнаружил его недоступность. Это вызовет пересмотр следующего шага для всех активных адресатов, ранее доступных через пропавшего соседа.
Кроме того, если пропавший сосед — маршрутизатор по умолчанию, то надо на какое-то время исключить его из кандидатов для алгоритма передачи. Например, при циклическом обходе списка маршрутизаторов достаточно перейти к следующему элементу в нем. Тогда пропавший маршрутизатор окажется в конце списка и хост выберет его в последнюю очередь; у бедняги будет время на то, чтобы прийти в себя.
Как мы видим, структуры данных эталонного хоста IPv6 не только привязаны к определенному сетевому интерфейсу, но и логически связаны между собой , как это схематически показано на рис. 5.23.
(рис 5.23) Структуры данных эталонного хоста IPv6 и связь между ними
Прямая связь заключается в следующем:
Обратная связь не менее важна и сводится вот к чему:
если NUD обнаруживает, что сосед стал недоступен, то это повод удалить из кэша DC все записи, ссылающиеся на него как на следующий шаг;
Это позволит хосту адаптироваться как к сбою маршрутизатора, так и к изменению топологии канала, когда бывший сосед становится доступен только через маршрутизатор.
Как именно хост примет во внимание недоступность маршрутизатора, зависит от его алгоритма управления списком маршрутизаторов по умолчанию. Принципиальный алгоритм [§6.3.6 RFC 4861] рекомендует только, чтобы, во-первых, заведомо или вероятно доступные маршрутизаторы получали приоритет и, во-вторых, в отсутствие заведомо или вероятно доступных маршрутизаторов список перебирался по кругу. Смысл этого алгоритма в том, чтобы не выбрать снова сбойный маршрутизатор до тех пор, пока хост не попытает счастья со всеми остальными пунктами списка.
Сведения о доступности маршрутизаторов поставляет механизм NUD, и может так оказаться, что в данный момент полной записи NC ни об одном из них нет. Это вовсе не значит, что все маршрутизаторы недоступны, — просто стали недоступны те из них, которыми хост до сих пор пользовался. Это повод проверить на работоспособность остальные маршрутизаторы в списке. Отсюда и возникает перебор списка по кругу.
Подведем итоги. В §5.1 мы "сконструировали" механизм розыска соседей, однако еще не сформулировали правил, когда именно узел IPv6 вправе считать удаленный адрес соседским и поводить его розыск. А розыск соседа, который соседом не является — это все равно, что охота на несуществующую черную кошку в темной комнате. Поэтому сейчас нам пришлось целенаправленно поработать над этим вопросом. В процессе мы пересмотрели простую модель канала и подсети, принятую в IPv4, и признали ее слишком ограниченной и не отвечающей требованиям современных канальных технологий. Взамен мы сформулировали для IPv6 принципиально иную модель, в которой маршрутизаторы координируют передачу данных хостами по каналу и за его пределы, а хосты, в свою очередь, обращаются к маршрутизаторам за сведениями о топологии канала, а не пытаются самостоятельно вычислить ее. Затем мы "создали" довольно простой протокол в рамках ND, который обеспечивает работу канала IPv6 согласно этой модели.
Исторической справедливости ради заметим, что хост IPv4 тоже мог следовать подобной линии поведения, когда у него не было полноценной таблицы маршрутов, а вместо нее были список маршрутизаторов по умолчанию и кэш маршрутов, уточняемый переадресовками [§3.3.1.2 RFC 1122]. Тем не менее, критерий соседства IPv4 был задан вполне однозначно и отвечал модели, когда все адреса в подсети, и только они, принадлежат соседями [§3.3.1.1 RFC 1122].
Как вы думаете, насколько оригинальна наша модель? Конечно же, ничто не ново под луной, и на ту же самую модель давно опирается стек протоколов ISO. В частности, взаимодействие хостов (конечных систем, ES) и маршрутизаторов (транзитных систем, IS) в нем обеспечивает протокол ES IS [ISO 9542; предыдущая версия доступна как RFC 995].
Подход ES IS к решению данной задачи можно даже признать более изящным и логически завершенным. Он не сводится к одним только переадресовкам, а включает в себя периодические объявления, которыми ES и IS извещают друг друга о своем существовании. Поэтому некое подобие розыска соседей в ES IS необходимо, только когда на канале нет ни одной IS. Даже в этом случае ES-источник не тратит время на предварительный розыск соседа, а просто шлет пакет с данными по групповому канальному адресу "все ES". ES-адресат, если он действительно подключен к каналу, принимает пакет в обработку и заодно высылает ES-источнику объявление, содержащее его индивидуальный канальный адрес. Тогда ES-источник кэширует канальный адрес соседа, и последующие пакеты уже идут по нему, а не группе "все ES".
Таким образом, ES по умолчанию полагает, что все адреса "на канале". В противоположность этому, хост IPv6 обязан по умолчанию считать, что "вне канала" находятся все адреса, кроме внутриканальных. Когда нет ни маршрутизатора, ни ручной настройки списка префиксов, хосты IPv6 могут обмениваться индивидуальными пакетами, только используя внутриканальные адреса, так как в этих условиях все остальные индивидуальные адреса находятся "вне канала" и потому недоступны.
Любопытно, что ранние стандарты IPv6 следовали модели ES IS, в которой все адреса по умолчанию были "на канале", однако впоследствии от нее полностью отказались [RFC 5942], потому что за ее внешней простотой скрываются различные осложнения [RFC 4943].
Для общего знакомства со стеком протоколов ISO и, в частности, с протоколом ES IS мы настоятельно рекомендуем замечательную книгу Ради
В завершение рассмотрим любопытный пример того, как отличается практика управления подсетями в IPv4 и IPv6. Как мы помним, в IPv4 была вполне допустима конфигурация сети, когда из подсети был "вырезан" префикс большей длины и назначен другому каналу. Скажем, префикс 192.0.2.0/24 назначен каналу К1, а 192.0.2.96/27 — каналу К2, как это показано на рис. 5.24.
(рис 5.24) Удаленный под-префикс в IPv4
Маршрутизаторы М1 и М2 естественно работали с такой конфигурацией, потому что префикс канала К2 был точнее, чем префикс канала К1, и диапазон адресов 192.0.2.96–192.0.2.127 однозначно маршрутизировался в направлении канала К2, тогда как остальные адреса вида 192.0.2.X оставались на канале К1. Тем не менее, хосты канала К1 не могли передавать пакеты хостам канала К2, потому что они разыскивали их на канале К1, вместо того чтобы обратиться к маршрутизатору М1. Обычным решением этой проблемы было или включить ARP proxy на маршрутизаторе М1, или распространить точный маршрут на 192.0.2.96/27 через М1 по всем хостам канала К1, вручную или протоколом маршрутизации.
Как бы мы стали решать ту же проблему в IPv6? Допустим, каналу К1 назначена подсеть 2001:DB8:1:2::/64, а каналу К2 — 2001:DB8:1:2:3:4::/96, как показано на рис. 5.25. Прежде всего, мы не можем не заметить, что длина префикса подсети, отличная от 64, нарушает адресную политику IPv6 из §2.6, хотя и не вступает в противоречие с базовым протоколом, так что назначение такого префикса остается на совести администратора сети.
(рис 5.25) Удаленный под-префикс в IPv6
Что же касается технического решения, то здесь мы видим, как минимум, три альтернативных подхода на основе стандартных механизмов IPv6:
Пусть читатель найдет способ составить такой список префиксов, используя минимально возможное число элементов. (Начните с доказательства того, что минимально необходимое число префиксов в таком списке равно 32. Затем попробуйте доказать, что кратчайшее решение — единственное. По ходу вы поймете, как устроен этот список.)
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.