Поля заголовка IPv4-пакета: Version, IHL, TTL, Protocol, флаги фрагментации и контрольная сумма.
Каков минимальный размер заголовка IPv4?
Что происходит, когда TTL пакета достигает нуля?
Какое значение поля Protocol соответствует TCP?
Почему в IPv6 убрали поле Header Checksum?
Что означает установленный флаг DF (Don't Fragment)?
Мы с вами посмотрели на то, как ведется взаимодействие в IP, посмотрели на то, что в IP есть у нас отправители-получатели, есть цепочка транзитных узлов между ними. И эти транзитные узлы по какому-то правилу смотрят в содержимое пакета или не смотрят в содержимое пакета и перекладывают эти самые пакеты между своими интерфейсами куда-то поближе к получателю. При этом IP в том виде, в котором его описали в RFC 760 и 791, на самом деле не специфицирует, как именно это должно происходить. Но логично предположить, что мы должны перекладывать пакеты именно поближе к получателю, а мы должны при этом знать, кто получатель такой и где он находится. И надо будет понимать, куда именно в пакете смотреть для того, чтобы понять, где получателя можно будет найти. Кто вообще он такой будет? Нам придется сейчас заглянуть в формат заголовка IPv4.
Замечу сразу, что в других версиях протокола IP формат, естественно, будет другой. Были те самые экспериментальные версии IPv0, IPv1. Они, считай, не назывались IP, они назывались TCP, Transmission Control Program. И у них в любом случае первые четыре бита указывали на номер версии протокола. У самой первой версии вкладывалось число, допустим, 1. У второй версии вкладывалось число 2. IPv4 — это первая версия, которая пошла в Production. И в поле версии вкладывается число 4. Это поле десятичное или, соответственно, 1, 0, 0, 0 двоичное. Это поле занимает 4 бита. И как раз это поле версии 4-битовое намекает на то, что всё, что дальше следует, действительно IPv4-пакет. Зачем это поле будет нужно? Опять же вспоминаем почтовых голубей. К нам прилетел голубь, у него вокруг лапки обвязана ленточка. Мы открываем эту ленточку, и мы должны её запихнуть в сканер для того,
чтобы получить содержимое этого IP-пакета. Или, допустим, у вас есть интерфейс Ethernet, по Ethernet пришел Ethernet-кадр, вы его получаете. Внутри написано, что лежит IP-пакет. Вы содержимое отдаёте обработчику IP. Обработчик IP дальше должен понять, то, что пришло, это действительно IP-пакет, или это даже гипотетически IP-пакетом быть не может. Всякие процедуры типа «давайте посчитаем чексуму» и прочее, сегодня они кажутся нам вполне естественными. Мы берём, считаем чексуму, если она сходится, значит, это IP-пакет. А на тот момент, когда всё это дело придумывалось, в конце 60-х годов, такая операция, как посчитать чексуму, была настолько вычислительно затратной, потому что тогда калькуляторы занимали полкомнаты, что были заложены в протокол механизмы, которые позволяли бы упростить жизнь обработчикам как можно чаще и сделать необходимыми как можно меньше вычислений.
И если вдруг вы получали что-то, и у этого что-то характерного признака IPv4-пакета не было, то вы дальше уже все эти чексумы и прочее просто не вычисляли. И самый простой способ проверить, что последовательность битов, которая пришла, потенциально может являться IPv4-пакетом — как раз поле версии для этого и было нужно. Если вы принимаете какую-то последовательность байт, бит, неважно, и вы смотрите в первые 4 бита и видите там число 4, вы говорите: окей, это действительно похоже на IPv4-пакет, по крайней мере, гипотетически это может быть он. Если мы посмотрели в первые 4 бита и поняли, что там лежит какое-то другое число, мы говорим: это в принципе точно не может быть IPv4-пакет, к нам оно пришло по ошибке, мы не считаем чексумы,
мы просто сразу выкидываем этот пакет. Он пришёл к нам мусорный. Так что это механизм, который позволял облегчить жизнь обработчику IP на старых роутерах, на старых получателях тоже. Дальше второе поле указывает на размер заголовка IP. Дело в том, что авторы протокола предусмотрели, что какие-то поля, которые им показались полезными, в заголовке будут. А ещё есть какие-то поля, которые авторы протокола не предусмотрели, потому что на момент придумывания этого протокола каких-то вещей могли ещё не изобрести. И было предположение, что потомкам, которые будут использовать IPv4, может понадобиться в заголовок записывать что-нибудь дополнительное, чего изначально Винтон Серф и Джон Постел, авторы протокола IPv4, не предусмотрели. Поэтому они сказали: у нас есть некий фиксированный заголовок,
фиксированная часть заголовка, и есть поле options, которое имеет переменную длину. Оно может либо отсутствовать вовсе, либо быть каким-то достаточно большим. И у него есть некий размер, который по факту это поле занимает. Авторы предположили, что если вдруг это поле будет задействоваться, то оно всегда будет задействоваться для вкладывания в заголовок какой-то полезной информации, которую авторы изначально не заложили, но которая впоследствии будет необходима. И договорённость по этому полю была следующая. Это поле всегда кратно 4 байтам по длине. Вы не можете положить туда 1 байт полезного чего-то, и всё. Вы можете положить только 4 байта чего-то полезного. И если вы хотите положить 1 байт чего-то полезного, то вы потом должны до границы 4 байт добить это всё дело мусором, так называемое поле padding, для того чтобы у вас заголовок всегда был выровнен по границе 4 байт. И из самого этого поля должно было быть понятно, сколько именно места оно занимает.
Получалось, что если у нас поле выровнено по границе 4 байт, то вы могли либо одну строчку в 4 байта положить в это поле, либо 2 строчки, либо 3 строчки. То есть 4 байта, 8 байт, 12 байт. И таким образом это поле могло разрастаться до нужного вам размера. Но бесконечно большим оно тоже быть не могло. И в поле IHL, Internet Header Length, отправитель вкладывал общий размер заголовка вместе с фиксированными полями и полем опций. И сюда вкладывалась именно длина заголовка в этих 4-байтовых строчках. Почему такое странное решение было принято? Дело в том, что компьютеры обрабатывают информацию, как правило, не побайтово. Они берут кусок данных и дальше начинают с этим куском работать. Если у вас 32-битная архитектура,
то вы берёте кусок данных 32 бита или 4 байта. Это вся первая строчка. И дальше начинаете с ним работать как с единым монолитным целым. У вас в регистр оно записывается целиком. Если у вас 16-битная архитектура, вы берёте, соответственно, одно машинное слово и два машинных слова. Машинное слово — это сколько за один раз помещается в одной операции данных. В один регистр, если хотите. Так вот, IPv4-заголовок выравнивается по границе 32 бит именно потому, что он изначально был предназначен для 32-битных архитектур. И в Internet Header Length вы указываете размер заголовка в этих самых машинных 32-битных словах. Именно поэтому у вас все эти поля выравнены по границе 4 байт или 32 бит. Первая строчка — 4 байта. Вторая строчка — 4 байта. Третья строчка — 4 байта. Четвёртая строчка — 4 байта. Пятая строчка — 4 байта. Поле с опциями состоит из некого целого числа строчек по 4 байта каждая. Именно для того, чтобы процессоры могли легко обработать эти поля,
чтобы им не приходилось работать в виде «возьми 17 бит из одной строчки, а потом 37 бит из другой строчки, и как-то сложи между собой». Он говорит: «возьми 32 бита, и там все поля, которые тебе нужны, они будут целостно лежать». В 4 бита поле IHL, Internet Header Length, вы можете положить не так много возможных вариантов. В 4 бита влезает самое маленькое число — это 0, 0, 0, 0. Это наше привычное число 0 десятичное. Или самое большое число, которое туда можно положить — это 1, 1, 1, 1 двоичное. Это число 15 десятичное. Поэтому в IHL можно положить значения от 0 до 15. Как было сказано, это число указывает размер заголовка в машинных словах. Поэтому гипотетически минимальный размер заголовка, который можно будет указать в этом поле, это 0.
А максимальный размер заголовка — это 60. 60 байт занимает сам заголовок. Но есть нюанс. Первая, вторая, третья, четвёртая и пятая строчка — они всегда есть. Поэтому на самом деле самое маленькое число, которое туда можно положить — это 0, 1, 0, 1. Двоичное, или пятёрка десятичное. Потому что раз, два, три, четыре, пять машинных слов есть в обязательном порядке. И размер заголовка варьируется от 5 до 15 машинных слов, то есть от 20 до 60 байт. Может быть такое, что есть только эти пять машинных слов, только фиксированные поля заголовка, и поле опций вообще отсутствует, оно занимает 0 байт, либо оно может занимать 4, 8, 12 и так далее, до 40 байт включительно. Чаще всего в современном мире поле опций вы не увидите. Оно не используется, и не используется по очевидной причине. Его должны понимать все узлы в цепочке между отправителем и получателем. И если бы вы гипотетически его использовали в интернете, то опции должны были поддерживаться всеми роутерами в цепочке. Любой роутер в интернете должен был поддерживать все опции, которые только бывают.
Этого не наблюдается именно потому, что опции были созданы для того, чтобы положить в заголовок что-то, что могло бы другими не поддерживаться. Поэтому по факту пакеты с опциями сегодня встретить в интернете нельзя. Дальше. Два поля DSCP и ECN используются для того, чтобы мог работать механизм Quality of Service. Не какой-то стандартный способ использования изначально был заложен для этих полей, а было просто указано, что сюда кладётся что-то для Quality of Service. А как именно Quality of Service должен использовать эти поля, в стандарте не указано. По факту есть отдельные расширения для протокола IP, которые указывают, что если всё-таки сюда что-то кладётся, оно должно использоваться неким более-менее стандартным предсказуемым образом. Но это, во-первых, расширения, поэтому они не обязательно поддерживаются всеми участниками. И, во-вторых, договорённости об использовании этих полей должны быть сделаны перед тем, как они по факту начинают использоваться. Поэтому если вдруг вы думаете, что сейчас вы возьмёте и запишете туда число, какое-нибудь, не знаю, 255 во все пакеты, которые вы отправляете в интернет,
и у вас внезапно интернет начнёт работать в 10 раз лучше — нет, это немножко не так работает. Вы должны будете заранее договориться о том, как именно заполнять эти поля, чтобы механизмы управления качеством доставки работали корректно. Дальше. У нас есть поле, которое называется Total Length. Это поле указывает на суммарный размер пакета вместе с заголовками, вместе с полезными данными. Отдельно есть поле IHL, Internet Header Length, которое указывает на размер заголовка,
и есть отдельно поле Total Length, которое указывает на суммарную длину пакета вместе с полезными данными. Если вдруг вы захотите посчитать размер полезных данных, то достаточно будет из Total Length вычесть IHL, и вы получите размер полезных данных. Дальше. Вторую строчку сейчас пропустим пока. Она будет рассказана отдельно. Третья строчка начинается с поля Time to Live. И если вдруг вы в сегодняшнем мире спросите кого-нибудь, что это за поле и как оно работает, с вероятностью близкой к единице вам скажут, что это количество роутеров, которые может пройти пакет, прыжков между роутерами. На самом деле это поле имеет говорящее название. Time to Live — это время жизни пакета. Время натурально в секундах. Вы, отправляя пакет, указываете, через сколько секунд это содержимое станет неактуальным. И есть договорённость, что каждый роутер, который перекладывает пакет между интерфейсами,
из этого поля вычитает то время, которое он потратил на обработку этого пакета. Это было актуально в тот момент, когда у вас роутеры и узлы связывались между собой линиями на скорости 300 бит в секунду. Представьте себе, что у вас есть звонок из условного Сиэтла во Флориду. Скорость — 300 бит в секунду, которые ваши модемы вытягивают. И вы по ним передаёте килобайтный пакет. Этот пакет будет идти 30 секунд по одному телефонному соединению. А если вам надо переложить пакет из одного интерфейса в другой, то есть вы по одному звонку принимаете этот пакет, по другому модему вы его отправляете кому-то третьему, вам 30 секунд надо будет принять этот пакет и 30 секунд на то, чтобы отправить. И пока вы передадите пакет до получателя, уже все ядерные бомбы прилетят и в радиоактивный пепел Америку-матушку распепелят. Поэтому вы можете сказать, что если мы отправляем какой-то пакет с информацией, которая через некоторое время потеряет актуальность — например, мы знаем, что время подлёта ракеты — это две минуты. Если мы даём сигнал на пуск ответной ракеты, то через две с половиной минуты этот пакет уже не нужен. Он, скорее всего, даже не дойдёт, но даже если дойдёт, уже нет смысла отправлять ответную ракету. Поэтому вы указывали натуральное время жизни в секундах для конкретного пакета.
И каждый роутер, который начинал принимать пакет, видит, когда к нему начинает приходить головка пакета, засекает время от начала приёма пакета до конца приёма, до самого хвостика. И это время он вычитает из TTL. И дальше пересылает пакет в какой-то другой интерфейс. Там уже следующий роутер вычитает время в секундах, которое потребовалось ему для приёма этого пакета на своём интерфейсе. Но есть нюанс. Была принята договорённость, что хотя бы единичку в любом случае надо вычитать. Если вдруг вы работаете на субсекундных скоростях, тратите меньше секунды на обработку пакета, то вычитаете вы как минимум единичку. И сегодняшние роутеры, конечно же, используют именно такие скорости. Сегодня вы никогда не будете работать со скоростями меньше, чем, скажем, мегабит в секунду. И на этих скоростях поле TTL превращается, по сути, эффективно в количество прыжков между роутерами.
Хотя изначально это было именно время в секундах. Дальше. Поле Protocol указывает на то, что внутри лежит. К вам пришёл какой-то пакет. Прекрасно. Пакет цельный. Прекрасно. У него в заголовке всё, что нужно. Написано даже, что это пришло вам. Дальше возникает вопрос: а что внутри лежит? Какому конкретно обработчику надо передать содержимое? И в поле Protocol вы указываете число — номер протокола. Например, если там TCP, то вы указываете шестёрку.
Если там UDP, вы указываете 17. Если там ICMP, вы указываете единичку. То есть формат того, что внутри лежит. Дальше. Двухбайтовая чексума, которая называется Header Checksum. Это чексума от заголовка. И в IPv4 действительно чексума считалась только от заголовка, но не от самих данных. Поэтому если вдруг вы принимаете какой-то пакет в IPv4 — не знаю, почтовый голубь прилетел, тот самый, у него вокруг лапки обвязано нечто напоминающее пакет, вы открываете ленточку, на которой там написано что-то, смотрите на поле версии, там 0100. Всё честно, это IPv4-пакет. Засовываете в сканер, оцифровываете, преобразуете в нули и единицы. И дальше вам надо понять, побилось оно по дороге или не побилось. Но если вдруг вы будете смотреть на чексуму всего, что пришло, то чексуму вам придётся вычислять от всего содержимого. И это может быть не очень удобно, потому что чексуму от всего вычислять просто долго. На тех мощностях, которые были в 60-х годах,
все эти дополнительные операции занимали достаточно много времени. Поэтому для того, чтобы не вычислять чексуму от полезных данных, IPv4 не предусматривал вообще возможности
проверить целостность полезных данных. Он проверял целостность только заголовка. Вы принимали почтового голубя, разворачивали, засовывали ленточку в сканер и считали чексуму только от первых 20 или 40, или 60 байт, сколько было написано в IHL. К вам прибежал, условно говоря, 100-байтовый пакет, вы смотрели, что у него написано в IHL, понимали, каков размер заголовка, и считали чексуму только от этого заголовка. И если она сходилась, вы говорили: этот пакет, по крайней мере, на уровне заголовка не побился, поэтому тому, что написано в source address, destination address и дальше, можно будет доверять. Если там написано, что этот пакет пришел именно нам и внутри лежит, условно говоря, TCP, то мы можем запустить наш обработчик TCP и передать ему содержимое. И пусть уже TCP занимается тем, что вычисляет, побились эти данные по дороге или не побились. И таким образом получается, что вы не заставляете все транзитные роутеры каждый раз при перекладывании пакета между интерфейсами считать чексуму от полезных данных.
Транзитные роутеры занимаются только тем, что необходимо для транзита. Если вдруг в какой-то момент данные сами покорёжатся, покорёженные данные дойдут до получателя, получатель про это уже узнает, отследит и каким-то образом перезакажет данные или что-то ещё. Но транзитные роутеры следят только за тем, чтобы заголовок был корректный, а за данными они вообще не следят. Source address, destination address. Здесь 32-битные IP-адреса. IP-адрес отправителя, IP-адрес получателя. В Ethernet, если вы помните, у нас тоже есть MAC-адрес отправителя, MAC-адрес получателя, и причём они идут в определённом порядке. Сначала MAC-адрес получателя, а потом MAC-адрес отправителя. И делается это вполне сознательно, потому что в Ethernet у нас много узлов получают одновременно кадр в общем проводе, в коаксиале, и дальше много узлов зажмуривается, а один действительный получатель не зажмуривается
и читает этот кадр. Поэтому чем раньше мы сделаем возможным зажмуриться всем ненужным узлам, тем лучше. В IPv4, если вы получили пакет, с большой вероятностью этот пакет предназначен именно вам, и очень маленькая вероятность того, что этот пакет вам не предназначен. Поэтому если вдруг вы что-то получили, надо проверить целостность, проверить, что пришёл корректный пакет, и всё остальное. Фактически это не настолько неважные задачи по сравнению с тем, чтобы проверить, что пакет пришёл именно вам, чтобы их как-то отдельно относить в конец. В Ethernet самой первой операцией была «это вам или не вам?» просто потому, что нужно сэкономить ресурсы всем остальным вычислительным узлам. Здесь, если вы всё равно уже приняли этот пакет, вам всё равно считать чексуму заголовка, вам всё равно проверять поле версии. И вам всё равно на всякий случай, в рамках регулярных проверок, не поломалось ли оно по дороге, не случилась ли какая-то ошибка, смотреть на поле destination address.
Поэтому это поле всего лишь одно среди равных. Оно не какое-то суперважное для того, чтобы с ним как-то обращаться особо. В Ethernet это было очень важное поле — кому идёт кадр. А в IP — нет. Оно регулярное поле, обычное, достаточно важное, как и многие другие. Destination address — кто получатель, source address — кто отправитель. Эти поля не меняются при транзите. Вообще практически все поля здесь не меняются при транзите. Давайте сейчас попробую нарисовать, какие поля могут меняться, и это будет на самом деле очень просто. У вас может меняться вот эта штука. Стандарт не специфицирует, что сюда вкладывать. Здесь одно поле, когда-то называлось type of service, простите, traffic class — это в IPv6. Потом его переименовали, разбили на две части. Это стало метка DSCP и коды ECN. Дальше может меняться и должно меняться time to live, и как следствие чексумма,
которая считается от всего заголовка в целом. Всё остальное не меняется при транзите. Иногда меняется поле опций. Транзитные роутеры могут дописывать в это поле некоторую информацию, если вдруг туда что-то нужно будет добавить. Но всё остальное не меняется при транзите. Вот у вас узел-отправитель, обычный компьютер. Он подключён к роутеру, роутер подключён к роутеру, роутер подключён к роутеру, и здесь компьютер-получатель. Они отправляют друг другу пакет. Этот пакет при передаче между роутерами может быть изменён вот в этой части, вот в этой части, вот в этой части и как следствие в чексумме. Всё остальное транзитные роутеры менять не могут. Единственное исключение, которое может быть — если вдруг у вас будет операция фрагментации, то строчка, которую мы с вами не разобрали, она будет как раз отвечать за фрагментацию, она может меняться. Но про это у нас будет рассказано отдельно дальше. Суммируем. Поле версии для IPv4 всегда 0100 в двоичной, или 4.
Поле IHL, Internet Header Length, указывает размер заголовка в машинных словах, минимум пятёрка, максимум 15. Размер заголовка из-за переменной длины поля опций может меняться от 20 байт до 60 байт. Поле опций — от 0 до 40. Метки DSCP и ECN, если вдруг вам это интересно, регулируются вот этими двумя стандартами. RFC 3168 для ECN, RFC 2474 для DSCP. Как они работают? Я сейчас рамочно вам расскажу, просто на пальцах, а вы, если вдруг вам интересно будет, углубитесь в тему самостоятельно. У нас есть два узла. Они друг с другом напрямую не связаны, они связаны через цепочку роутеров. Каким-то образом они вот так соединены. Вот, клавиатурку нарисовал. Узел А отправляет узлу Б пакет.
Узел А отправил пакет, он прошёл через первый роутер, дошёл до второго роутера, а у второго роутера здесь возникла перегрузка, потому что у него ещё сбоку в этот же интерфейс валится какой-то лишний трафик и по какой-то другой трассе он проходит, поэтому этот интерфейс перегружен. Там наблюдаются потери пакетов, потому что очередь на отправку растёт, и растёт как-то угрожающе. Если пакет будет попадать в эту очередь, то он фактически пройдёт через неё чудом, потому что если у нас интерфейс перегружен, то там наблюдаются потери пакетов из-за перегрузки. Часть пакетов здесь будет теряться, часть будет проходить нормально и будет корректно доставляться до получателя. Если мы хотим сделать так, чтобы нагрузка на этот интерфейс стала меньше, мы можем использовать эти самые поля ECN, Explicit Congestion Notification. Метка ECN двухбитовая, и есть там два бита:
FECN и BECN. Forward Explicit Congestion Notification и Backward Explicit Congestion Notification. Когда вы отправляете пакетик дальше через интерфейс, который подвергается перегрузке, и у вас этот пакет прошёл через эту перегрузку чудом, вы выставляете флажок FECN, Forward Explicit Congestion Notification. В изначальном пакете там были нули, а когда пакет проходит через перегрузку, в нём выставляется флажочек. Один-ноль. Один — это тот самый флажок FECN, и ноль — это BECN. Пакет бежит, бежит, бежит, бежит, доходит до получателя. Узел B принимает такой пакет, и он понимает: этот пакет прошёл через сеть чудом, потому что где-то в сети есть перегрузка. И если вдруг он захочет отправить какой-то встречный пакет, другой уже, абсолютно никак не связанный с этим, просто другой пакет, который идёт в обратную сторону на узел A, то у этого пакета как раз выставляется флажочек BECN,
Backward Explicit Congestion Notification. Это означает, что пакет идёт на узел, который перед этим отправлял нашему узлу свои пакеты, и эти пакеты прошли по сети чудом. Этот пакет доходит до получателя, до узла A, и узел A понимает, что те пакеты, которые он отправляет в сторону узла B, проходят через сеть чудом, а это значит, что нагрузка где-то там слишком высокая, и он эту нагрузку может снизить. Он может отправлять пакеты реже, если захочет. И таким образом, если все узлы поддерживают эту штуку, то нагрузка на этот интерфейс постепенно будет снижаться до тех пор, пока дропы не прекратятся. После этого дропы прекратятся, и узлы потихоньку расслабят булки и начнут снова отправлять трафика сколько захотят. Это флажки FECN — по дороге туда где-то мы испытали перегрузку. И BECN — кто-то по дороге в другую сторону испытал перегрузку,
а мы об этом обратно сообщаем отправителю в совершенно другом, никак не связанном пакете. DSCP — это 6 бит, используется для того, чтобы заказать определённый класс доставки. QoS — это большая тема, мы про неё будем на АСИНД-2 детально говорить. Если вкратце, не надо выставлять эти самые поля DSCP, ECN в совершенно произвольное значение, если у вас нету договорённости о том, как их использовать, с тем, кому вы их проставляете. Самый плохой сценарий, который можно придумать — это сказать: мне не нравится, как у меня интернет работает, я сейчас возьму и в эти 6 бит поставлю, не знаю, число 63, что с моей точки зрения должно символизировать самый крутой тип трафика, который только можно придумать. Нет, ваш провайдер с очень большой вероятностью эту метку просто не читает. Поэтому он её перезатрёт и будет использовать это поле для каких-то своих нужд, и поэтому совершенно бесполезно его выставлять по своему усмотрению.
Дальше. Total Length уже, в принципе, сказал. Размер поля 16-битный, в 16 бит можно вписать число от 0 до 65535. 0 вписывать не принято традиционно, потому что у нас как минимум 20-байтный заголовок есть. Если вы хотите отправить совершенно пустой пакет без какого-либо вложения, так тоже не очень принято делать, но гипотетически вы должны будете хотя бы 20 байт заголовка отправить всё равно. Любой узел, который работает в IP, обязан работать с размером пакетов как минимум 576. Больше тоже можно. Гипотетически можно представить себе узел, который работает с килобайтными пакетами, с двухкилобайтными пакетами, с трёхкилобайтными пакетами. Но это не обязательное требование. Обязательное требование — что абсолютно любой узел может работать с пакетами до вот такого размера. Если вы отправите пакет 500 байт, его абсолютно точно все обязаны прочитать корректно.
Если вы отправите пакет килобайт, 1000 байт, гипотетически может быть ситуация, при которой какой-то узел этот пакет прочитать не сможет. Сегодня в интернете принято работать с пакетами 1500 байт. Это дополнительное требование, которое уже просто в индустрии зародилось. Сегодняшняя вся техника по факту с пакетами такого размера работает корректно. Но тем не менее по стандарту 576 байт. Некоторые протоколы, с которыми мы сегодня работаем, которые в том числе являются основой для современного интернета, они на эти 576 байт завязаны довольно жёстко. Например, DNS. Если вдруг вы с ним не сталкивались, он старается отправлять такие датаграммы, которые гарантированно в эти 576 байт влезли. А если вдруг вы хотите использовать какие-то модерновые расширения, то вам надо будет использовать уже не совсем тот DNS, который изначально был придуман. Такую схему EDNS0. Расширение для DNS. Это уже более новый протокол.
И наконец, те поля, про которые я вам ничего не рассказывал. Три поля. Identification, Flags и Fragment Offset. Это три поля, которые управляют процедурой, которая называется фрагментация. Смысл заключается в следующем. Если вдруг у вас есть два узла, которые поддерживают большие пакеты, например, двухкилобайтные. Узел А находится в Сиэтле, узел Б находится во Флориде. И вы хотите отправить пакет размером два килобайта из Сиэтла во Флориду. Но у вас нет прямого канала. Вы должны будете отправлять этот пакет какому-то транзитному роутеру, который переложит этот пакет дальше, тот переложит дальше, и в итоге он дойдёт до получателя. Да, узел отправителя такой размер поддерживает. Да, узел получателя такой размер поддерживает. Но совершенно не факт, что все транзитные роутеры поддерживают такой размер. Потому что роутеры гарантированно поддерживают 576 байт. А поддерживают ли они 2 килобайта — совершенно не факт. Максимально допустимый размер в современных сетях, принятый в индустрии — это то, что любой роутер, любой канал
может передавать пакеты размером до 1500 байт. Всё, что больше, может и не пройти, может не пролезть в интерфейс. Например, стандартное вложение в Ethernet — как раз, помните, 1500 байт? У нас максимальный размер кадра вместе с заголовками 1518 байт. Если 18 байт отрезать, то получится 1500 байт вложения. Если вы берёте 2 роутера, соединяете просто обычным Ethernet, полтора килобайта он вам пропустит. А 2 килобайта уже не пропустит. И если вдруг вам надо отправить пакет, двухкилобайтный, к примеру, через интерфейс, который столько не позволяет, то у вас есть 2 варианта, что сделать. Либо выкинуть такой пакет, сказать: я не могу его пропустить через интерфейс, он не пролезает. Это справедливо — вы не можете это сделать, значит, убивайте такой пакет. Либо есть дополнительная процедура, которая называется фрагментация, когда вы пакет расчленяете на части, каждую часть отправляете независимо.
К вам пришёл пакет-переросток, вы видите, что он не пролезает в интерфейс выхода, взяли, отрезали от него ручки-ножки, и каждую часть расчленённого пакета отправили отдельно независимо. Приклеили заголовок к каждому фрагменту и отправили по сети. Каждый фрагмент меньше, чем полтора килобайта, поэтому получатель свои данные всё равно получит, просто в виде расчленёнки. А дальше его задача — собрать всё это обратно в оригинальный пакет. На картинке как раз такая ситуация нарисована. У нас есть какая-то среда, в которой поддерживаются большие пакеты двухкилобайтные, и по ней прибегает пакет, который как раз два килобайта и имеет размер. Но дальше наш роутер пытается понять, что с ним сделать, и он говорит: я не могу этот пакет пропустить через интерфейс полтора килобайта, поэтому я буду его резать на части. Первая часть отправляется — сколько можно, столько и отправляется, полтора килобайта,
а во вторую часть отправляется всё остальное. Здесь надо заметить, что вторая часть получится не 500 байт. В первой части, которая будет 1500 байт, содержится оригинальный заголовок, 20 байт этого заголовка и 1480 байт полезного мяса. А оставшаяся часть мяса, 500 байт, плюс ещё новый заголовок, который копируется от оригинального, в итоге дадут вам 520 байт. Давайте разберём детально поведение каждого из этих трёх полей и посмотрим на то, как происходит процедура фрагментации. Первое поле — это Identification. Поле 16-битное, и когда вы отправляете какие-то пакеты к какому-то конкретному узлу, вам не следует отправлять два разных пакета, которые отправляются рядышком друг с другом, с одинаковыми номерами Identification. Я максимально непонятно сказал, наверное, да?
Смысл в том, что если вы отправляете два пакета подряд одному и тому же получателю, вам следует поставить разные идентификаторы. Самый простой способ, как это можно сделать: вы можете просто все пакеты, которые вы отправляете, нумеровать и делать из них сквозную нумерацию. Первый, второй, третий, четвёртый. 16 бит вам дают номера с нулевого по 65535. Для тех скоростей, которые были актуальны в конце 60-х годов, когда всё это придумывалось, проблемы здесь не возникало, потому что вы один пакет отправляете там 30 секунд. Потом один пакет отправили, второй придумали пакет с идентификатором, большим на единичку, и его отправляете. Потом третий, потом четвёртый. Пока вы по кругу все эти 65 тысяч пакетов переберёте, у вас уже вселенная закончится от тепловой смерти, поэтому проблем здесь авторы протокола не видели. Но они предусмотрели, что теоретически скорости передачи данных могут быть чуть больше,
чем 300 бит в секунду. Поэтому они сказали: не обязательно отправлять пакеты, именно пронумерованные первый, второй, третий. Вы можете использовать идентификатор совершенно произвольным образом. Главное сделать так, чтобы подряд пакеты с одинаковыми идентификаторами были разнесены во времени на какое-то достаточно большое значение. Оригинальная формулировка была, что они должны быть разнесены по времени на максимальное число, которое можно вложить в поле TTL. Если вы отправили один пакет с идентификатором, например, 13, то второй пакет с таким же идентификатором 13 должен быть отправлен не раньше, чем максимальное число TTL, то есть 256. Больше, чем 255 — 256. Это приблизительно 4,5 минуты. Для тех скоростей, ещё раз подчеркну, которые были актуальны в конце 60-х, начале 70-х, проблемы не было.
Проблема стала понятна, когда IP начал разрастаться и идти в массы, и когда появились всякие мегабитные интерфейсы и прочее. И стало понятно, что эти идентификаторы — первый, второй, третий, четвёртый, пятый — даже если их просто перебирать один за одним, они очень быстро закончатся. И мы вынуждены будем по второму кругу их отправлять, и у нас получится, что один пакет мы отправили с идентификатором 13, и потом меньше чем через 0,1 секунды второй пакет вынуждены отправлять с таким же идентификатором 13. И время между ними, естественно, будет сильно меньше, чем максимальный TTL в секундах, который мы можем задать. На это авторы протокола ничего не ответили, потому что было уже поздно. Протокол ушёл в продакшн в откровенно сыром состоянии, поэтому некоторые вещи были актуальны и адекватны реалиям конца 60-х годов, но они абсолютно неадекватны реалиям третьего тысячелетия. Поэтому да, может быть такое, что вы отправите два пакета с одинаковыми идентификаторами,
главное, чтобы они совсем рядышком друг с другом не шли, но да, они по времени могут быть разнесены достаточно слабо. Дальше. Для чего вообще это поле Identification делается? Для того, чтобы если вдруг где-то кто-то будет выполнять фрагментацию, чтобы получатель, когда получит фрагменты пакетов, мог понять, какой фрагмент относится к какому пакету. Смысл заключается в том, что вы берёте пакет на отправителе и отправляете его по сети, а потом следующий за ним тоже отправляете. Вы говорите: это пакет с номером 1, это пакет с номером 2, это пакет номер 3. И эти пакеты у вас по сети бегут. Бегут, бегут, бегут, и они приходят на получателя. У нас какая-то сеть тут есть. Здесь где-то выполняется фрагментация на каком-то транзитном роутере, и получатель получает на самом деле половинку первого пакета и другую половинку первого пакета. И потом половинку второго пакета
и другую половинку второго пакета. И потом половинку третьего пакета и другую половинку третьего пакета. Ему их надо собрать между собой. Может так получиться, что, допустим, половинка второго пакета придёт самой первой, потому что все пакеты у нас идут разными трассами. И они могут прийти вперемешку. И для того, чтобы разложить полученные фрагменты по кучкам — эта кучка — все кусочки первого пакета, эта кучка — все кусочки второго пакета, эта кучка — все кусочки третьего пакета — как раз поле Identification используется. Мы взяли, за некоторое время собрали все кусочки, в которых написано одинаковое Identification, и говорим: в этой кучке, похоже, лежит полный пакет с номером 13. Мы её собираем и получаем готовый пакет с номером 13. Фактически в этой ситуации мы говорим: идентификатор 13 освобождается, если вдруг сейчас следующий пакет с номером 13 придёт, мы уже сможем собрать его заново. Не может быть такого, что у нас два пакета с идентификаторами 13
прошли через трассу, порезались на части, и мы спутали пакет с идентификатором 13, отправленный сейчас, и пакет с идентификатором 13, отправленный чуть позже. Когда-то давным-давно это же поле использовалось для того, чтобы отследить, задублировался ли пакет при прохождении по сети. Я вам говорил, что в IP пакеты могли дублироваться. И если вдруг у нас такое происходило, то получатель, для того чтобы понять, что он какой-то пакет уже обрабатывал, мог посмотреть на это поле Identification и сказать, что я уже пакет с таким идентификатором за последние TTL секунд видел, поэтому этот пакет мне уже неинтересен. Но с ростом скоростей это всё стало неактуально, сегодня такое поведение запрещено. Но изначально оно было. Мы просто говорили, что мы недавно обрабатывали пакет с идентификатором 13, поэтому в ближайшие, условно говоря, 2,5 минуты его появляться не должно. Но, сами понимаете, на современных скоростях,
когда мы оперируем гигабитами и 16-битным размером этого поля с идентификаторами, абсолютно никакого смысла такое поведение не имеет. Есть ли какое-то ограничение на количество дроблений пакета? Есть. Смотрите, я сказал, что любой узел обязан поддерживать пакеты до 576 байт размером. Это ограничение сверху того, что обязан поддерживать узел. Всё, что больше 576, не обязано поддерживаться. Есть ограничение также и снизу. Какого размера пакет обязан поддерживаться любым узлом, а то, что меньше, уже совершенно не факт, что вообще будет допустимо. Смысл там в следующем. У вас есть 20 байт заголовка, плюс 40 байт поле опций, плюс ещё хотя бы что-то надо положить внутрь. Этого чего-то может быть минимум 8 байт.
Нельзя, если вы отправляете пакет, который был порезан на части, делать фрагмент, у которого меньше 8 байт мяса получается. И поэтому 68 байт в итоге — это минимальный размер фрагмента, который вы можете получить с учётом заголовков. Абсолютно любой узел должен уметь обрабатывать 68-байтные пакеты, и абсолютно любой канал, которым связаны роутеры, обязан такого размера пакеты пропускать без фрагментации. Если вы отправляете фрагмент 68 байт, он гарантированно пролезет через любой канал без дробления. Да, это я к чему? К тому, что по полю Identification вы как раз могли отследить, какие фрагменты к какому изначальному пакету относились. Fragment Offset — это поле, которое позволяет после того,
Как вы, приняв на получателе кучку фрагментов, поймёте, какой фрагмент идёт первым, какой вторым, какой третьим. К нам пришёл, условно говоря, мешок, и в нём отдельно ручка, отдельно ножка, отдельно головка. Понятно, что весь этот мешок содержит фрагменты одного большого пакета. Но какой из них первый? Где у него головка, где у него ручка? По внешнему виду их непонятно, по самим байтам. И нужно будет каким-то образом этот порядок восстановить. И для того, чтобы этот порядок восстановить, используется как раз поле fragment offset. Поле имеет немножко кривую длину — 13 бит. Здесь возникает вопрос, почему 13? Какое-то очень сильно кривое число. На самом деле я сейчас воспроизведу то машинное слово, в котором как раз три наших поля. Identification — 16 бит. Дальше. Fragment offset — 13 бит. И здесь ещё три бита — флаги. Получается, что здесь у нас 16 бит,
но три бита были съедены под флаги, поэтому осталось всего 13. И в 13 бит можно вписать число от нуля до, соответственно, 2 в какой-то степени. 13 минус 1. Это у нас как раз будет… Сейчас соображу. 8192. Да, 8191. Вы можете вписать в это поле от нуля до 8000 с копейками различные значения. 2 в 13 степени минус 1. И возникает вопрос, а что в это поле мы хотим вписать? Самый простой вариант, который можно объяснить на пальцах. Вы туда вписываете номер байта оригинального пакета, начиная с которого конкретно в этом фрагменте содержатся боевые данные.
У нас есть оригинальный пакет. У него самый первый байт был нулевой, потом где-то 8, где-то 16, где-то 24. Они все пронумерованы. И здесь у нас был, не знаю, 500-й байт, потом 508-й и так далее. А 500… да, 508-й. Подозрительно как-то. Ну ладно. И, соответственно, вы можете взять и сказать: первый фрагмент мы берём вот такой, второй фрагмент мы берём вот такой, третий фрагмент мы берём вот такой. И в первом фрагменте у нас получается от нуля до 23-го байта, условно говоря, 23-го байта мяса. Во втором фрагменте с 24-го байта по 47-й. В третьем фрагменте с 48-го по какой-то ещё. Мы в каждом конкретном фрагменте говорим, что это кусочек оригинального пакета,
в котором содержится мясо оригинального пакета, начиная с определённого байта. И когда мы берём и собираем наш исходный пакет на получателе, мы говорим: окей, находим сначала фрагмент с номером 0. Потом мы смотрим, какой у него размер полезных данных. А мы это можем понять. У нас есть поле Total Length. И после того, как мы взяли и посчитали размер мяса, мы получаем номер, с какого по какой байт конкретно в этом фрагменте оригинальный пакет представлен. И дальше, если мы находим первый фрагмент, и в нём с нулевого по 23-й байт представлен, значит нам надо найти фрагмент, у которого начиная с 24-го и дальше что-то есть. Мы находим такой фрагмент и говорим: у нас 24-й байт. Следовательно, он как раз вплотную прилегает к первому фрагменту без каких-либо потерь. И мы отматываем всё, что в нём есть, и находим, что последний байт, который в нём был, имел 47-й номер.
Дальше мы должны найти тот самый фрагмент, у которого 48-й байт и далее будет передаваться. Вот такая простая идея. Но возникает вопрос. Если мы будем вкладывать во fragment offset просто номер байта, то нам не хватит этих самых 13 битов. Потому что максимальное значение, которое туда можно положить, — 8000, а оригинальный размер пакета мог быть от 20, если вы помните, до 65535 байт. Без учёта заголовков там могло быть от 0 до 65515 байт. И получается, что если мы будем прямо туда просто натурально номер байта вкладывать, то не влезет, там сильно не хватает. И поэтому договорились. Если мы что-то режем на части, то мы режем на части по границе 8-байтовых блоков. И, соответственно, каждый из этих блоков мы нумеруем. И когда мы вкладываем во fragment offset что-нибудь,
мы вкладываем именно номер блока. Опять же, если вам кажется, что это как-то очень сложно, на самом деле вы можете очень легко получить номер байта из номера этого 8-байтового блока. Просто берёте, умножаете на 8. Если у нас есть, соответственно, какое-то число, не знаю, тысяча, которое мы во fragment offset кладём, соответственно, на самом деле это значит, что в конкретно этом фрагменте передаётся мясо оригинального кадра с боевыми байтами, начиная с 8-тысячного. И если вдруг вы подумаете чуть-чуть детально на тему того, что такое в двоичной логике умножить на 8, это на самом деле, если у нас есть какое-то число, не знаю, какое-то совершенно произвольное двоичное число, какое-то совершенно случайное двоичное число, мы его хотим умножить на 8 десятичное. Это фактически эквивалентно тому, что мы справа к нему припишем три нолика. Получится число 1 0 1 0 1 0 1 1 1 0.
И дальше ещё 0 0 0. Когда мы хотим умножить какое-то число на 2, мы приписываем к нему справа нолик. Когда мы хотим умножить число на 4, мы приписываем справа два нолика. На 8, соответственно, три нолика. В десятичной системе счисления точно то же самое. Если мы хотим в десятичной системе счисления взять и умножить число на 10, мы просто приписываем справа нолик. То же самое в двоичной системе счисления. И, соответственно, да. Если мы берём какой-то пакет, у которого байты нумеруются от 0 до 2 в 16 степени минус 1, делим на фрагменты, то в каждом конкретном фрагменте может быть байт с нумерацией от 0 до 65535. Это, соответственно, 2 в 16 степени минус 1. И когда мы берём и делим этот пакет на блоки, то, соответственно, каждый номер первого байта будет обязательно заканчиваться на 3 нолика.
Эти 3 нолика просто не пишутся. И когда мы записываем во fragment offset номер байта, мы фактически пишем номер байта без последних трёх ноликов. Или это то же самое, что писать номер 8-байтового блока. У самого первого фрагмента самый первый байт, естественно, будет иметь номер 0. И если мы берём оригинальный пакет, который ещё не был порезан на части, он несёт, естественно, вообще все свои байты, в том числе и байты с нулевым номером. Поэтому у самого оригинального пакета во fragment offset, естественно, лежит число 0. Так. Далее. Оставшиеся 3 бита — это поле флаги. На самом деле из трёх битовых флагов, которые там есть, используется только два. Один зарезервирован и всегда равен нулю. Второй называется don't fragment. Это значит, что вы запрещаете фрагментацию,
и тем самым вы говорите, что если пакет не пролезает в какой-то интерфейс, не надо пытаться фрагментировать его, надо просто его пристрелить. И будет очень мило, если тот, кто прибил такой пакет, даст понять отправителю, что пакет не дошёл до получателя. Но это мы в разговоре про ICMP обсудим. И, соответственно, последний флаг называется MF, more fragments. MF будет выставляться во всех фрагментах, кроме последнего. Если вы побили пакет на части, то у самого первого, у второго, у третьего, у всех фрагментов, кроме последнего, этот флажок будет выставлен в единицу. Типа это ещё не конец. Смысл в том, что если у вас есть оригинальный пакет, он с нулевого по, не знаю, на две части его поделим, 47-й байтик. С нулевого по 23-й и с 24-го по 47-й байты у нас получились
из оригинального 48-байтового пакета. Мы поделили его на две части. До получателя дошло два фрагмента. Первый фрагмент — с нулевого по 23-й. Он говорит: окей, это первый фрагмент, мы видим это по номеру 0. У второго фрагмента — с 24-го по 47-й. Это фрагмент, который вплотную пристыковывается к первому фрагменту. Они как раз друг с другом защёлкиваются, как два кусочка пазла. И мы получили первый кусочек пазла, второй кусочек пазла. Но непонятно, в какой момент надо остановиться и прекратить ждать следующие кусочки. Потому что то, что мы видим начало этого пакета оригинального, — это хорошо. Но мы не видим, где конец. Так вот, конец будет как раз у того фрагмента, у которого MF будет выставлен в 0. У нас есть оригинальный наш пакет, который двухкилобайтный пробежал. У него первый битик выставлен в 0. Это зарезервированный битик.
Второй битик выставлен в 0. Фрагментация не запрещена. И третий битик указывает на то, что это сам себе единственный фрагмент. Он же первый фрагмент, он же последний фрагмент. Это просто оригинальный пакет. Наш роутер выполняет фрагментацию. Он говорит: я побил этот пакет на две части. У самого первого пакета первый бит — по-прежнему 0. Он зарезервирован. Второй флаг выставлен в 0. Если вдруг следующий роутер в цепочке будет вынужден заняться ещё раз фрагментацией, потому что у него будет какая-то среда с 1400 байтами, например, пропускания, он может фрагмент 1500-байтовый порезать на дальнейшие две части, на ещё дальнейшие фрагменты. Никаких проблем здесь не будет. Он разрешает дальнейшую фрагментацию, и он говорит: это не последний фрагмент. More fragments выставляется в единицу. И у нас с первого по 23-й байты,
и, соответственно, 0, 0, 1. Это 0 — он всегда 0. Это 0 означает, что фрагментация возможна, и единица означает, что это не последний фрагмент. Дальше второй фрагмент, который он отправляет, говорит: первый битик 0 — зарезервированный, второй битик 0 — тоже можно выполнять фрагментацию, и третий битик — это конец, 0, 0, 0. Как следствие, третий фрагмент можно не ждать, его не будет никогда, потому что у нас собрался весь пазл целиком. Обратите внимание, я неявно это сказал, но если вы выполнили фрагментацию, если вы порезали пакет на части, вы можете фрагменты потом в последующем снова фрагментировать. Никаких проблем нет. Вы только должны руководствоваться той же самой логикой, что если у вас есть фрагмент, который нужно дальше фрагментировать ещё дополнительно, то в любом случае вы руководствуетесь логикой по этому самому more fragments,
что вы корректно выставляете 0 в каком-то фрагменте, только если действительно в оригинальном фрагменте вы там видели 0. Если к вам пришёл фрагмент, у которого этот битик уже был выставлен в единицу, значит, вы во всех своих фрагментах, даже в самом последнем, выставляете единицу в more fragments. Равным образом вы ведёте себя с fragment offset. Если к вам приходит какой-то фрагмент, который вы должны будете повторно фрагментировать, никаких проблем нет, только соблюдайте сквозную нумерацию, которая была в оригинальном кадре. Так, зачем нужны пакеты в 65536 байт? В 68-м году, в 69-м году, когда всё это дело заказывалось, авторам казалось это неплохой идеей. На самом деле в современных реалиях часто бывают нужны пакеты существенно большего размера. Если говорить про тот же самый IPv6, то там разрешены пакеты до гигабайта. И опять же, голубиная почта нам намекает, зачем это может быть нужно.
Если у нас всё-таки в радиоактивный пепел испепелили, никакие средства связи не работают, только голубиная почта и осталась. В этой ситуации может быть неплохой идеей взять голубя, приклеить к нему на лапку microSD-флешку и, соответственно, отправить в его родную голубятню. Потому что голуби всё равно никак не завязаны на средства связи. И в этой ситуации ему намного прикольнее будет, конечно, лететь не с 60 килобайтами полезной нагрузки, а всё-таки с, например, гигабайтом. Но IPv4 такими объёмами оперировать не мог. Более того, авторы протокола исходили из той технической базы, которая у них была в конце 60-х, начале 70-х годов. Памяти в 640 килобайт, казалось, на тот момент будет достаточно не только всем, но это ещё даже внукам тех, кто это всё дело проектировал, тоже казалось так. Поэтому на узлах, которые могли отправлять и принимать IP-пакеты, много памяти просто не было. И те самые 65536 байт —
это был огромный задел на будущее, который, впрочем, не пригодился. Не всё, что авторы протокола делали, не всё из этого реально пошло в массы. Опять же, в том числе и потому, что протокол был экспериментальный. А так, в реальности, максимальный размер пакета, который по факту можно было отправлять, Он был как раз ограничен теми самыми 576 байтами. И, как правило, на большинстве узлов именно 576 байт был размер буфера под пакет. Если вы принимали пакет, он не мог быть ни 1500 байт, ни 1000 байт. А в 70-х годах, когда все это делалось, реальные имплементации IP оперировали пакетами именно 576-байтовыми, именно потому, что такой размер памяти компьютеры могли выделить под буфер. А 65 килобайт, сами понимаете, на тот момент казалось непозволительной роскошью. Я вам больше скажу. На самом деле, в компьютерах,
которые сильно позже появились, чем 74-й год, 78-й год, те самые приставки, Dendy, помните, еще в советское время были? Это же конец 80-х, начало 90-х годов все это разработки были. Там память 64 килобайта часто была всего. В конце 80-х годов память 64 килобайта на всю систему казалась очень-очень крутой памятью. И использовались всякие грязные трюки, как можно сэкономить вплоть до отдельных байт этой памяти, чтобы у вас больше ее оставалось под какие-то служебные нужды. Вплоть до того, что на видеопамяти экономили. Поэтому да, размер IP-пакетов 65 килобайт, он 64 килобайта, он, конечно, был с очень большим запасом. Чтобы вы могли, например, целиком содержимое экрана отправить в одном IP-пакете. К примеру.
И еще много осталось бы. Так, ладно. Вот три поля у нас. Identification, flags и fragment offset. Давайте разберемся на примере, как они работали. Вот у нас слева пришел пакет 2 килобайта. Надо справа выплюнуть этот пакет с полуторакилобайтным MTU. Максимальный размер пакета, который можно отправить в канале, именно в IP, называется MTU, Maximum Transmission Unit. Я уже говорил, что в Ethernet такого термина нету, но часто производители его все равно указывают, и каждый под этим термином имеет в виду что-то свое. Если говорить про CISCO, то CISCO под словом MTU в Ethernet имеет в виду размер содержимого, который можно положить в Ethernet. Очень часто другие производители под термином MTU в Ethernet или L2 MTU или что-то аналогичное будут иметь, например, в виду максимальный размер кадра, который вы можете отправить. Поэтому термин MTU в Ethernet
никоим образом не стандартизирован. А вот IP MTU – это абсолютно стандартная вещь. И, соответственно, Maximum Transmission Unit – это максимальный размер IP-пакета вместе с заголовками, который вы можете по сети отправить. У нас есть IP-пакет. Мы его должны расчленить на части. Первый фрагмент, который мы будем отправлять, он будет содержать, понятное дело, некоторое количество полезных данных из оригинального пакета. В оригинальном пакете у нас было 20 байт заголовка плюс 1980 байт мяса. IHL – 20 байт, 20 байт заголовка. Total Length – 2 килобайта. Здесь, на самом деле, конечно, не 20 байт, здесь на самом деле число 5 лежало, что опций нету. Дальше. Total Length – 2 килобайта как раз намекает на то, что из 20 байт заголовка и 1980 байт полезных данных у нас как раз общий размер и складывается. Identification – какой-то случайный, произвольный, неважно какой.
Флаги – все по нулям. Это пакет, который разрешает фрагментацию, и это самый последний фрагмент из всех, которые есть. Fragment offset намекает на то, что это еще и самый первый фрагмент из всех, которые есть. Он сам самый первый, он сам самый последний, он же единственный фрагмент, который вообще существует. Он сам себе лучший друг. TTL какой-то, 128, например. Что внутри лежит? Единичка, например, указывает на то, что внутри лежит протокол ICMP. Например, пинганули кого-нибудь, указали размер, чтобы пакеты получились двухкилобайтные. Довольно частое явление. Чек-сумма. Какая-то чек-сумма. Здесь показана нулевая. Могло же так случиться случайно, что чек-сумму посчитали, и она получилась нулевая. Она какая-то, случайная. И дальше IP-шники отправителя-получателя, которые, как мы помним, при передаче по сети не изменяются. Дальше заголовок ICMP 8 байт,
например, 8 байт. И заголовок закончился, дальше начинают передаваться боевые данные, просто какое-то мясо. 1972 байта. Мы выполняем фрагментацию, и заголовок у первого фрагмента будет наследоваться в большей части от оригинального пакета. Он практически без изменений будет передаваться. Версия 4. Сколько занимает машинных слов заголовок? По-прежнему пятерка, 20 байт. Дальше. Total length 1500. Мы не смогли двухкилобайтный пакет прокинуть, поэтому сказали: берем, отправляем первые 1500 байт, остальное как получится. Identification сохраняется, как здесь было, так оно здесь и сохраняется. Мы разрезали пакет на части, и чтобы сказать, что все части, которые у нас получились, имеют отношение к одному и тому же пакету, сохранили идентификаторы. Дальше. Флаги проставили 001, это пакет, который мы разрешаем резать на части,
и конкретно это фрагмент, причем фрагмент не последний. Fragment offset указываем, начиная с какого 8-байтового блока здесь передаются данные. Учитывая, что это первый фрагмент, у него все просто. Это начиная с нулевого байта и с нулевого 8-байтового блока все передается. Из поля TTL мы вычли единичку, дальше указали, что внутри лежит. Мы сохранили это из оригинального заголовка, мы это не трогали. Транзитному роутеру вообще неинтересно, что внутри лежит. Он это просто скопировал бездумно, бездушно, и все. Чек-сумма тоже какая-то здесь есть, она будет заново считаться, это не та же самая чек-сумма. У нас заголовок поменялся, и чек-сумма тоже поменялась. И source, destination тоже здесь есть. Дальше передается 1480 байт оригинального содержимого. Отсюда мы взяли первые 1480 байт и, соответственно, их сюда принесли. Здесь будет какой-то заголовок,
и начнут передаваться боевые данные. Начало этих боевых данных. Все, что не попало в первый фрагмент, должно передаваться дальше. У нас это будет 500 байт. Соответственно, мы их 500 байт положили. И дальше мы их обрастаем заголовком. Заголовок, опять же, копируется по большей части из оригинального пакета. Что внутри лежит? IPv4. Сколько заголовка внутри лежит? Пятерка. Total length. 500 байт плюс 20 байт заголовка. 520. Identification сохраняется. Флаги по нулям. Означает, что это, в принципе, можно, если вдруг будет нужно, порезать еще на части. Но если что, это уже самый последний фрагмент. Fragment offset указывает на то, начиная с какого 8-байтового блока здесь конкретно передаются данные. Это число 00B9 шестнадцатеричное, оно, если мне память не изменяет, 185 какое-то такое кривое число.
00B9. Давайте посмотрим в калькуляторе. Калькулятор. Так. Какое у нас там? B9. B9. 185. Десятичное. Это номер блока 185. B9 шестнадцатеричное. Это 185 десятичное. И если мы умножим 185 на 8, то мы получим как раз, что это с 1480-го байта и далее содержится полезная информация. Она действительно тут именно с такого номера и передается. TTL. Вычли единичку. Что внутри лежит? ICMP. Чек-сумма. Опять какая-то чек-сумма. Она здесь не нулевая должна быть, она какая-то произвольная должна быть. И source, destination — IP-шники, опять же, сохраняются оригинальные. Они просто тупо копируются. Обратите внимание на то, что у первого фрагмента написано, что лежит внутри — это ICMP.
И при этом действительно есть ICMP-шный заголовок. У второго фрагмента написано, что лежит внутри ICMP, но заголовка ICMP нету. Там просто какое-то случайно выдранное мясо лежит. И эта ситуация вполне возможна, когда вы декларируете, что внутри лежит что-то. А по факту формат данных, который вы там будете видеть, совсем не совпадает с тем, что вы ожидали увидеть. Это нормальная ситуация в случае фрагментации. Так, по поводу поля TTL. Уже, в принципе, все рассказал. Это изначально время жизни пакета в секундах. Название TTL, Time to Live, следовало читать буквально. Сколько в секундах жить этому пакету. Договаривались, что каждый транзитный роутер вычисляет время, которое потребовалось на прием и обработку пакета, и вычитает это время из поля TTL при передаче дальше. Если вы получали 0 или меньше, то вы пакет отбрасывали прямо посередине транзита,
потому что нет смысла дальше занимать канал и отправлять его получателю, все равно информация уже протухла. Но договорились, что как минимум единичка вычитается в любом случае. Соответственно, сегодня при скоростях, которые характерны в 2019 году для обработки пакетов, чаще всего вы пакеты обрабатываете за срок сильно меньше, чем одна секунда, поэтому вычитается всегда именно единичка. Начальное значение у поля TTL обычно выставляется произвольным образом, но, как правило, в какое-то красивое число, либо 64, либо 128, либо 255, и делается это в следующей логике. Чем больше число вы поставите, тем, соответственно, дальше вы этот пакет можете допулять в прыжках между роутерами. Но сегодня в интернете, как правило, вы будете видеть расстояние между двумя самыми удаленными точками в сети не больше, чем 30 роутеров. Поэтому, в принципе, вы можете выставить значение TTL в значение 64,
и оно точно дойдет до любой точки в интернете. Можно даже 40 поставить или 50, если вдруг с вашей стороны много каких-то служебных роутеров, с другой стороны сети тоже много каких-то служебных роутеров, плюс вы находитесь в разных частях планеты, из Намибии пытаетесь отправить пакет в какую-нибудь Папуа — Новую Гвинею, в этой ситуации роутеров у вас может быть много, и вы можете столкнуться с ситуацией, что если вы поставите 32, к примеру, то вам не хватит этих самых TTL для того, чтобы пропихать пакет из Намибии в Новую Гвинею. Но если вы поставите 64, то это почти всегда будет достаточно для того, чтобы все было хорошо, и в самые сумасшедшие сети пакет прошел из одной части интернета в другую. В какой ситуации это поле может быть полезно сегодня? Понятное дело, что для указания именно протухания пакета, что он слишком долго по сети бежал, это уже не актуально.
Это поле сегодня используется по факту как простейшая защита от петли маршрутизации. Если у вас есть два роутера, каждый из них должен руководствоваться логикой, что транзитные пакеты, которые адресованы не ему, он должен перекладывать куда-то поближе к получателю. При этом процесс выбора интерфейса и выбора канального адреса соседа, который находится ближе к получателю, не специфицирован. Каждый роутер принимает решение независимо и, возможно, не в той же логике, что и сосед. И может быть такая ситуация, что на какой-то роутер приходит пакетик, и в нем написано, это надо отправить Васе. И два роутера начинают играть в волейбол. Один говорит: я считаю, что роутер Б находится ближе, чем я, к Васе, поэтому он должен отправить этот трафик, пакет куда-то в сторону Васи. А роутер Б принимает такой пакет от А и говорит: я считаю, что роутер А находится ближе к Васе, и пусть он отправляет этот пакет дальше по сети.
И они начинают друг другу перекидывать этот пакет. В Ethernet у нас подобная ситуация заканчивается тем, что пакеты навечно в петле зависают, и все узлы, которые формируют эту петлю, просто падают в стопроцентную нагрузку. Все каналы, которые формируют эту петлю, падают в стопроцентную нагрузку. А если там еще и broadcast какой-то завис, то все вообще падает целиком и полностью. В IP эта штука не происходит именно благодаря полю TTL. У нас есть пакет, в нем в TTL, допустим, 64 написано. И волейбол у нас по факту будет происходить не бесконечно. Первый отправит другому, скажет 63, второй первому отправит, скажет 62, третий отправит 61. И так они посчитают до нуля, до тех пор, пока с их точки зрения этот пакет не протухнет. И этот пакет выкинут. Поэтому петля маршрутизации в IP — явление неприятное, но не настолько криминальное, как в Ethernet. Поле «протокол» указывает, что внутри лежит.
Это однобайтовое значение. Соответственно, размер поля не очень большой. Значение, которое туда можно вложить, присваивает международное агентство по именованию и нумерации интернета — IANA. Некоторые ключевые значения вы должны будете запомнить на экзамене. Их будут спрашивать, и в реальной жизни они вам тоже понадобятся. Единичка — ICMP. Это протокол, который мы дальше будем с вами изучать. Шестерка — TCP, 17 — UDP. Эти три значения надо просто знать как «Отче наш». В принципе, бывают и другие. Так, далее. Чексума. Уже все про нее рассказал. Используется алгоритм Internet Checksum. Не CRC. Не CRC-16, не CRC-32, а Internet Checksum. Это очень простой вычислительный алгоритм. Он неплохо обнаруживает ситуацию вида «один случайный битик побился».
Но это не цифровая подпись. Он не защищает от умышленных искажений. Это не механизм, который позволит гарантированно обнаруживать ошибки, особенно если их много. Это такой простенький механизм, который был актуален в 60-х, в 70-х годах, когда все это дело придумывалось, для того, чтобы не сильно перегружать транзитный роутер. И считается Internet Checksum в IP только от заголовка. IP-адреса — это 32-битные числа. Вы указываете, кто и кому отправил данные. И дальше при транзите эти поля не меняются. Обратите внимание, в IP-заголовке нет полей маски, в IP-заголовке нет полей роутера, шлюза, gateway или чего-либо еще. Есть два IP-адреса и больше ничего. Когда я вам показывал формат IP-заголовка, я говорил: посмотрите на этот заголовок,
здесь много чего есть. Но здесь надо запомнить то, что в этом заголовке есть по смыслу, и особенно надо запомнить то, чего там нет. В IP-заголовке есть только IP-адрес отправителя и только IP-адрес получателя. Масок IP-отправителя, масок IP-получателя, IP-адреса шлюза, MAC-адреса шлюза, MAC-адреса роутера, который выдал вам адрес — всего этого нет. Только два адреса. Отправитель и получатель. Каждый по 32 бита. Поле опций. Я уже сказал, что опции вы в реальном мире не встретите, потому что фактически, если вы туда что-то вкладываете, это означает, что это что-то нестандартное. И вы, отправляя пакет с чем-то нестандартным, неизбежно сталкиваетесь с тем, что некоторые роутеры в интернете это не будут поддерживать. Как следствие, этот пакет не сможет быть доставлен до получателя. Поэтому идея была красивая, что потомкам может понадобиться что-то в заголовок класть, то, что изначально не было предусмотрено.
Но она оказалась, к сожалению, нежизнеспособной. Именно потому, что это что-то нестандартное, что изначально предусмотрено не было. Сама суть этого поля, к сожалению, оказалась вредительской. Сегодня вы будете часто видеть, что если вы попытаетесь отправить такие пакеты с опциями в интернет, они будут отбрасываться как небезопасные. Это все, что я хотел бы вам рассказать про заголовок IP. Пожалуйста, осмыслите все те поля, про которые я вам рассказал, зачем они нужны. Поймите логику того, что можно положить и чего нельзя положить в заголовок IP. И никогда не пытайтесь рассказывать мне, что в заголовке IP, например, есть такое поле, как маска. Нет там его.